1
- const getMALHashMap = async ( type : string , username : string , list : Array < any > = [ ] , page = 1 ) : Promise < any > => {
1
+ import { sleep , getOperationDisplayName } from './util' ;
2
+ import * as Log from './Log' ;
3
+ import * as Dom from './Dom' ;
4
+ import { MALHashMap , MALItem , MediaDate , FormattedEntry , FullDataEntry } from './Types' ;
5
+
6
+ const createMALHashMap = ( malList : Array < MALItem > , type : string ) : MALHashMap => {
7
+ const hashMap : MALHashMap = { } ;
8
+ malList . forEach ( item => {
9
+ hashMap [ item [ `${ type } _id` ] ] = item ;
10
+ } ) ;
11
+ return hashMap ;
12
+ }
13
+
14
+ const getMALHashMap = async ( type : string , username : string , list : Array < MALItem > = [ ] , page = 1 ) : Promise < MALHashMap > => {
2
15
const offset = ( page - 1 ) * 300 ;
3
- const nextList = await fetch ( `https://myanimelist.net/${ type } list/${ username } /load.json?offset=${ offset } &status=7` ) . then ( res => res . json ( ) ) ;
16
+ const nextList = await fetch ( `https://myanimelist.net/${ type } list/${ username } /load.json?offset=${ offset } &status=7` )
17
+ . then ( res => res . json ( ) ) ;
4
18
if ( nextList && nextList . length ) {
5
19
await sleep ( 1000 ) ;
6
20
return getMALHashMap ( type , username , [ ...list , ...nextList ] , page + 1 ) ;
7
21
}
8
22
Log . info ( `Fetched MyAnimeList ${ type } list.` ) ;
9
- const fullList = [ ...list , ...nextList ] ;
10
- return createMALHashMap ( fullList , type ) ;
23
+ return createMALHashMap ( [ ...list , ...nextList ] , type ) ;
11
24
}
12
25
13
- const malEdit = ( type , data ) =>
26
+ const malEdit = ( type : string , data : MALItem ) =>
14
27
fetch ( `https://myanimelist.net/ownlist/${ type } /edit.json` , {
15
28
method : 'post' ,
16
29
body : JSON . stringify ( data )
@@ -20,7 +33,7 @@ const malEdit = (type, data) =>
20
33
throw new Error ( JSON . stringify ( data ) ) ;
21
34
} ) ;
22
35
23
- const malAdd = ( type , data ) =>
36
+ const malAdd = ( type : string , data : MALItem ) =>
24
37
fetch ( `https://myanimelist.net/ownlist/${ type } /add.json` , {
25
38
method : 'post' ,
26
39
headers : {
@@ -35,7 +48,7 @@ const malAdd = (type, data) =>
35
48
throw new Error ( JSON . stringify ( data ) ) ;
36
49
} ) ;
37
50
38
- const getStatus = ( status ) => {
51
+ const getStatus = ( status : string ) => {
39
52
// MAL status: 1/watching, 2/completed, 3/onhold, 4/dropped, 6/plantowatch
40
53
// MAL handles REPEATING as a boolean, and keeps status as COMPLETE
41
54
switch ( status . trim ( ) ) {
@@ -55,7 +68,7 @@ const getStatus = (status) => {
55
68
}
56
69
}
57
70
58
- const buildDateString = ( date ) => {
71
+ const buildDateString = ( date : MediaDate ) => {
59
72
if ( date . month === 0 && date . day === 0 && date . year === 0 ) return null ;
60
73
const dateSetting = Dom . getDateSetting ( ) ;
61
74
const month = `${ String ( date . month ) . length < 2 ? '0' : '' } ${ date . month } ` ;
@@ -67,7 +80,7 @@ const buildDateString = (date) => {
67
80
return `${ day } -${ month } -${ year } ` ;
68
81
}
69
82
70
- const createMALData = ( anilistData , malData , csrf_token ) => {
83
+ const createMALData = ( anilistData : FormattedEntry , malData : MALItem , csrf_token : string ) : MALItem => {
71
84
const status = getStatus ( anilistData . status ) ;
72
85
const result = {
73
86
status,
@@ -83,7 +96,7 @@ const createMALData = (anilistData, malData, csrf_token) => {
83
96
month : anilistData . startedAt . month || 0 ,
84
97
day : anilistData . startedAt . day || 0
85
98
} ,
86
- } ;
99
+ } as MALItem ;
87
100
88
101
result [ `${ anilistData . type } _id` ] = anilistData . id ;
89
102
@@ -106,9 +119,9 @@ const createMALData = (anilistData, malData, csrf_token) => {
106
119
result . num_read_volumes = malData . manga_num_volumes || 0 ;
107
120
}
108
121
}
122
+ } else {
109
123
// Non-completed item; use Anilist's counts
110
124
// Note the possibility that this count could be higher than MAL's max; see if that creates problems
111
- } else {
112
125
if ( anilistData . type === 'anime' ) {
113
126
result . num_watched_episodes = anilistData . progress || 0 ;
114
127
} else {
@@ -119,15 +132,7 @@ const createMALData = (anilistData, malData, csrf_token) => {
119
132
return result ;
120
133
} ;
121
134
122
- const createMALHashMap = ( malList , type ) => {
123
- const hashMap = { } ;
124
- malList . forEach ( item => {
125
- hashMap [ item [ `${ type } _id` ] ] = item ;
126
- } ) ;
127
- return hashMap ;
128
- }
129
-
130
- const shouldUpdate = ( mal , al ) =>
135
+ const shouldUpdate = ( mal : MALItem , al : MALItem ) =>
131
136
Object . keys ( al ) . some ( key => {
132
137
switch ( key ) {
133
138
case 'csrf_token' :
@@ -162,23 +167,14 @@ const shouldUpdate = (mal, al) =>
162
167
// In certain cases the next two values will be missing from the MAL data and trying to update them will do nothing.
163
168
// To avoid a meaningless update every time, skip it if undefined on MAL
164
169
case 'num_watched_times' :
165
- {
166
- if ( ! mal . hasOwnProperty ( 'num_watched_times' ) ) {
167
- return false ;
168
- }
169
- if ( al [ key ] !== mal [ key ] ) {
170
- return true ;
171
- } ;
172
- return false ;
173
- }
174
170
case 'num_read_times' :
175
171
{
176
- if ( ! mal . hasOwnProperty ( 'num_read_times' ) ) {
172
+ if ( ! mal . hasOwnProperty ( key ) ) {
177
173
return false ;
178
174
}
179
175
if ( al [ key ] !== mal [ key ] ) {
180
176
return true ;
181
- }
177
+ } ;
182
178
return false ;
183
179
}
184
180
default :
@@ -194,3 +190,48 @@ const shouldUpdate = (mal, al) =>
194
190
}
195
191
}
196
192
} ) ;
193
+
194
+ const syncList = async ( type : string , list : Array < FullDataEntry > , operation : string ) => {
195
+ if ( ! list || ! list . length ) {
196
+ return ;
197
+ }
198
+ Log . addCountLog ( operation , type , list . length ) ;
199
+ let itemCount = 0 ;
200
+ // This uses malEdit() for 'completed' as well
201
+ const fn = operation === 'add' ? malAdd : malEdit ;
202
+ for ( let item of list ) {
203
+ await sleep ( 500 ) ;
204
+ try {
205
+ await fn ( type , item . malData ) ;
206
+ itemCount ++ ;
207
+ Log . updateCountLog ( operation , type , itemCount ) ;
208
+ } catch ( e ) {
209
+ console . error ( e ) ;
210
+ Log . info ( `Error for ${ type } <a href="https://myanimelist.net/${ type } /${ item . id } " target="_blank" rel="noopener noreferrer">${ item . title } </a>. Try adding or updating it manually.` ) ;
211
+ }
212
+ }
213
+ }
214
+
215
+ export const syncType = async ( type : string , anilistList : Array < FormattedEntry > , malUsername : string , csrfToken : string ) => {
216
+ Log . info ( `Fetching MyAnimeList ${ type } list...` ) ;
217
+ let malHashMap = await getMALHashMap ( type , malUsername ) ;
218
+ let alPlusMal = anilistList . map ( item => Object . assign ( { } , item , {
219
+ malData : createMALData ( item , malHashMap [ item . id ] , csrfToken ) ,
220
+ } ) ) as Array < FullDataEntry > ;
221
+
222
+ const addList = alPlusMal . filter ( item => ! malHashMap [ item . id ] ) ;
223
+ await syncList ( type , addList , 'add' ) ;
224
+
225
+ // Refresh list to get episode/chapter counts of new completed items
226
+ Log . info ( `Refreshing MyAnimeList ${ type } list...` ) ;
227
+ malHashMap = await getMALHashMap ( type , malUsername ) ;
228
+ alPlusMal = anilistList . map ( item => Object . assign ( { } , item , {
229
+ malData : createMALData ( item , malHashMap [ item . id ] , csrfToken ) ,
230
+ } ) ) ;
231
+ const updateList = alPlusMal . filter ( item => {
232
+ const malItem = malHashMap [ item . id ] ;
233
+ if ( ! malItem ) return false ;
234
+ return shouldUpdate ( malItem , item . malData )
235
+ } ) ;
236
+ await syncList ( type , updateList , 'edit' ) ;
237
+ } ;
0 commit comments