Skip to content

Commit 5f40b4a

Browse files
authored
New: Support MyAnimeList tracking
1 parent 08d66ed commit 5f40b4a

7 files changed

Lines changed: 257 additions & 11 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
3131
- Support compressed files with password [`9a6ef8e`](https://github.com/ollm/OpenComic/commit/9a6ef8e0a363e72e98634e827c50189bb2841047)
3232
- Show release notes in new version dialog [`e572128`](https://github.com/ollm/OpenComic/commit/e572128aecad5811fb2f1a7b1690bbfe1ebabe82)
3333
- Improved extract performance of big files [`5c50739`](https://github.com/ollm/OpenComic/commit/5c507391939c9e56cb1d304df9b3824116de67f4)
34+
- Support MyAnimeList tracking
3435

3536
##### 🐛 Bug Fixes
3637

scripts/tracking.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ async function track(chapter = false, volume = false, onlySite = false)
118118
{
119119
console.log('Get chapters and volumes number');
120120

121-
const data = await sitesScripts[site].getComicData();
121+
const data = (await sitesScripts[site].getComicData()) || {};
122122
setTrackingChapters(site, indexMainPathA, data.chapters, data.volumes);
123123
}
124124
}
@@ -305,7 +305,9 @@ async function currentTrackingDialog(site)
305305
});
306306

307307
const path = dom.indexMainPathA();
308+
308309
const data = await sitesScripts[site].getComicData(siteData.tracking.id);
310+
if(data === null) return; // Invalid session
309311

310312
handlebarsContext.trackingResult = data;
311313
handlebarsContext.siteData = siteData;
@@ -453,6 +455,8 @@ async function searchComic(site, title = false)
453455
handlebarsContext.trackingSiteKey = site;
454456

455457
const results = await sitesScripts[site].searchComic(title);
458+
if(results === null) return; // Invalid session
459+
456460
handlebarsContext.trackingResults = results;
457461

458462
$('.tracking-search').html(template.load('dialog.tracking.search.results.html'));

scripts/tracking/anilist/anilist.js

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,12 @@ async function searchComic(title)
5757
{
5858
const response = await fetch('https://graphql.anilist.co', options);
5959

60-
if(response.status == 200)
60+
if(response.status == 400 || response.status == 401)
61+
{
62+
tracking.invalidateSession(site.key, true);
63+
return null;
64+
}
65+
else if(response.status == 200)
6166
{
6267
const json = await response.json();
6368
const results = (json.data?.Page?.media || []).map(function(media) {
@@ -124,7 +129,12 @@ async function getComicData(siteId)
124129
{
125130
const response = await fetch('https://graphql.anilist.co', options);
126131

127-
if(response.status == 200)
132+
if(response.status == 400 || response.status == 401)
133+
{
134+
tracking.invalidateSession(site.key, true);
135+
return null;
136+
}
137+
else if(response.status == 200)
128138
{
129139
const json = await response.json();
130140

@@ -138,8 +148,8 @@ async function getComicData(siteId)
138148
chapters: +chapters || 0,
139149
volumes: +volumes || 0,
140150
progress: {
141-
chapters: +(mediaListEntry?.progress || 0),
142-
volumes: +(mediaListEntry?.progressVolumes || 0),
151+
chapters: +mediaListEntry?.progress || 0,
152+
volumes: +mediaListEntry?.progressVolumes || 0,
143153
},
144154
};
145155
}
@@ -229,7 +239,7 @@ async function track(toTrack)
229239
{
230240
const response = await fetch('https://graphql.anilist.co', options);
231241

232-
if(response.status == 400)
242+
if(response.status == 400 || response.status == 401)
233243
{
234244
tracking.invalidateSession(site.key, true);
235245
}
@@ -290,10 +300,6 @@ async function track(toTrack)
290300

291301
fetch('https://graphql.anilist.co', options);
292302
}
293-
else
294-
{
295-
console.error(error);
296-
}
297303
}
298304
catch(error)
299305
{
1.76 KB
Loading
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
var site = {}, controller = false;
2+
3+
function setSiteData(siteData)
4+
{
5+
site = siteData;
6+
}
7+
8+
// Search comic/manga in site
9+
async function searchComic(title)
10+
{
11+
if(controller) controller.abort();
12+
controller = new AbortController();
13+
14+
const variables = new URLSearchParams({
15+
q: title,
16+
page: 1,
17+
perPage: 10,
18+
fields: 'title,main_picture', // 'title,main_picture,synopsis',
19+
});
20+
21+
const options = {
22+
method: 'GET',
23+
headers: {
24+
'Authorization': 'Bearer '+site.config.session.token,
25+
'Accept': 'application/json',
26+
},
27+
signal: controller.signal,
28+
};
29+
30+
try
31+
{
32+
const response = await fetch('https://api.myanimelist.net/v2/manga?'+variables.toString(), options);
33+
34+
if(response.status == 400 || response.status == 401)
35+
{
36+
tracking.invalidateSession(site.key, true);
37+
return null;
38+
}
39+
else if(response.status == 200)
40+
{
41+
const json = await response.json();
42+
const results = (json.data || []).map(function(item) {
43+
44+
const node = item.node || {};
45+
46+
return {
47+
id: node.id,
48+
title: node.title,
49+
image: node.main_picture.medium,
50+
// synopsis: media.synopsis || null,
51+
};
52+
53+
});
54+
55+
return results;
56+
}
57+
}
58+
catch(error) {}
59+
60+
return [];
61+
}
62+
63+
// Return data of comic/manga
64+
async function getComicData(siteId)
65+
{
66+
const options = {
67+
method: 'GET',
68+
headers: {
69+
'Authorization': 'Bearer '+site.config.session.token,
70+
'Accept': 'application/json',
71+
},
72+
};
73+
74+
try
75+
{
76+
const response = await fetch('https://api.myanimelist.net/v2/manga/'+siteId+'?fields=title,main_picture,num_chapters,num_volumes,synopsis,my_list_status,status', options);
77+
78+
if(response.status == 400 || response.status == 401)
79+
{
80+
tracking.invalidateSession(site.key, true);
81+
return null;
82+
}
83+
else if(response.status == 200)
84+
{
85+
const json = await response.json();
86+
87+
if(json.id)
88+
{
89+
return {
90+
title: json.title,
91+
image: json.main_picture.medium,
92+
// synopsis: synopsis,
93+
chapters: +json.num_chapters || 0,
94+
volumes: +json.num_volumes || 0,
95+
progress: {
96+
chapters: +json?.my_list_status?.num_chapters_read || 0,
97+
volumes: +json?.my_list_status?.num_volumes_read || 0,
98+
// status: json?.my_list_status?.status || '',
99+
},
100+
};
101+
}
102+
}
103+
}
104+
catch(error) {}
105+
106+
return {};
107+
}
108+
109+
// Loging to site
110+
async function login()
111+
{
112+
const challenge = crypto.hash('sha512', crypto.randomUUID(), 'hex');
113+
const url = await tracking.getRedirectResult(site.key, 'https://myanimelist.net/v1/oauth2/authorize?response_type=code&client_id='+site.auth.clientId+'&code_challenge='+challenge+'&redirect_uri=opencomic://tracking/myanimelist&response_type=code');
114+
const code = url.searchParams.get('code') || url.searchParams.get('token');
115+
116+
if(!code)
117+
return {valid: false};
118+
119+
const variables = new URLSearchParams({
120+
grant_type: 'authorization_code',
121+
client_id: site.auth.clientId,
122+
redirect_uri: 'opencomic://tracking/myanimelist',
123+
code: code,
124+
code_verifier: challenge,
125+
});
126+
127+
const options = {
128+
method: 'POST',
129+
headers: {
130+
'Content-Type': 'application/x-www-form-urlencoded',
131+
'Accept': 'application/json',
132+
},
133+
body: variables.toString(),
134+
};
135+
136+
try
137+
{
138+
const response = await fetch('https://myanimelist.net/v1/oauth2/token', options);
139+
140+
if(response.status == 200)
141+
{
142+
const json = await response.json();
143+
return {valid: true, token: json.access_token};
144+
}
145+
}
146+
catch(error) {}
147+
148+
return {valid: false};
149+
}
150+
151+
// Track comic/manga
152+
async function track(toTrack)
153+
{
154+
const options = {
155+
method: 'GET',
156+
headers: {
157+
'Authorization': 'Bearer '+site.config.session.token,
158+
'Accept': 'application/json',
159+
},
160+
};
161+
162+
try
163+
{
164+
const response = await fetch('https://api.myanimelist.net/v2/manga/'+toTrack.id+'?fields=title,main_picture,num_chapters,num_volumes,synopsis,my_list_status,status', options);
165+
166+
if(response.status == 400 || response.status == 401)
167+
{
168+
tracking.invalidateSession(site.key, true);
169+
}
170+
else if(response.status == 200)
171+
{
172+
const json = await response.json();
173+
174+
const totalChapters = +json.num_chapters || 0;
175+
const {status: userStatus, num_chapters_read: userChapters, num_volumes_read: userVolumes} = json?.my_list_status || {};
176+
177+
let status, chapters, volumes;
178+
179+
// Status
180+
if(totalChapters && toTrack.chapters && toTrack.chapters == totalChapters)
181+
status = 'completed';
182+
183+
// Chapters
184+
if(toTrack.chapters && (!userChapters || toTrack.chapters > userChapters))
185+
chapters = toTrack.chapters;
186+
187+
// Volumes
188+
if(toTrack.volumes && (!userVolumes || toTrack.volumes > userVolumes))
189+
volumes = toTrack.volumes;
190+
191+
const variables = new URLSearchParams();
192+
if(status) variables.append('status', status);
193+
if(chapters) variables.append('num_chapters_read', chapters);
194+
if(volumes) variables.append('num_volumes_read', volumes);
195+
196+
if(!status && !chapters && !volumes)
197+
return; // Nothing to update
198+
199+
const options = {
200+
method: 'PUT',
201+
headers: {
202+
'Authorization': 'Bearer '+site.config.session.token,
203+
'Content-Type': 'application/x-www-form-urlencoded',
204+
'Accept': 'application/json',
205+
},
206+
body: variables.toString(),
207+
};
208+
209+
fetch('https://api.myanimelist.net/v2/manga/'+toTrack.id+'/my_list_status', options);
210+
}
211+
}
212+
catch(error)
213+
{
214+
console.error(error);
215+
}
216+
}
217+
218+
module.exports = {
219+
setSiteData: setSiteData,
220+
searchComic: searchComic,
221+
getComicData: getComicData,
222+
login: login,
223+
track: track,
224+
};

scripts/tracking/tracking-sites-keys.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ const authKeys = {
22
anilist: {
33
clientId: 27257,
44
clientSecret: 'hIVMwj6ojwd1JXUij2iXG7r50BfFo1kKLTvVHE4a',
5-
}
5+
},
6+
myanimelist: {
7+
clientId: '66ede34f8b00b1cc6c3480ab3923fda4',
8+
},
69
};
710

811
module.exports = authKeys;

scripts/tracking/tracking-sites.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@ const sites = [
77
trackingVolume: true, // Supports volume tracking
88
url: 'https://anilist.co/',
99
},
10+
{
11+
key: 'myanimelist',
12+
name: 'MyAnimeList',
13+
description: 'Anime and manga Database and Community',
14+
trackingChapter: true, // Supports chapter tracking
15+
trackingVolume: true, // Supports volume tracking
16+
url: 'https://myanimelist.net/',
17+
},
1018
];
1119

1220
const trackingSitesKeys = require(p.join(tracking.scriptsPath(), 'tracking-sites-keys.js'));

0 commit comments

Comments
 (0)