mirror of
https://github.com/NoCrypt/migu.git
synced 2026-01-11 20:10:22 +00:00
237 lines
8.7 KiB
JavaScript
237 lines
8.7 KiB
JavaScript
import { get, writable } from 'simple-store-svelte'
|
|
import { defaults } from './util.js'
|
|
import IPC from '@/modules/ipc.js'
|
|
import { toast } from 'svelte-sonner'
|
|
import Debug from 'debug'
|
|
|
|
const debug = Debug('ui:anilist')
|
|
|
|
export let profiles = writable(JSON.parse(localStorage.getItem('profiles')) || [])
|
|
/** @type {{viewer: import('./al').Query<{Viewer: import('./al').Viewer}>, token: string} | null} */
|
|
export let alToken = JSON.parse(localStorage.getItem('ALviewer')) || null
|
|
/** @type {{viewer: import('./mal').Query<{Viewer: import('./mal').Viewer}>, token: string} | null} */
|
|
export let malToken = JSON.parse(localStorage.getItem('MALviewer')) || null
|
|
|
|
let storedSettings = { ...defaults }
|
|
|
|
let scopedDefaults
|
|
|
|
try {
|
|
storedSettings = JSON.parse(localStorage.getItem('settings')) || { ...defaults }
|
|
} catch (e) {}
|
|
try {
|
|
scopedDefaults = {
|
|
homeSections: [...(storedSettings.rssFeedsNew || defaults.rssFeedsNew).map(([title]) => title), 'Continue Watching', 'Sequels You Missed', 'Planning List', 'Popular This Season', 'Trending Now', 'All Time Popular', 'Romance', 'Action', 'Adventure', 'Fantasy']
|
|
}
|
|
} catch (e) {
|
|
resetSettings()
|
|
location.reload()
|
|
}
|
|
|
|
/**
|
|
* @type {import('simple-store-svelte').Writable<typeof defaults>}
|
|
*/
|
|
export const settings = writable({ ...defaults, ...scopedDefaults, ...storedSettings })
|
|
|
|
settings.subscribe(value => {
|
|
localStorage.setItem('settings', JSON.stringify(value))
|
|
})
|
|
|
|
profiles.subscribe(value => {
|
|
localStorage.setItem('profiles', JSON.stringify(value))
|
|
})
|
|
|
|
export function resetSettings () {
|
|
settings.value = { ...defaults, ...scopedDefaults }
|
|
}
|
|
|
|
export function isAuthorized() {
|
|
return alToken || malToken
|
|
}
|
|
|
|
window.addEventListener('paste', ({ clipboardData }) => {
|
|
if (clipboardData.items?.[0]) {
|
|
if (clipboardData.items[0].type === 'text/plain' && clipboardData.items[0].kind === 'string') {
|
|
clipboardData.items[0].getAsString(text => {
|
|
if (text.includes("access_token=")) { // is an AniList token
|
|
let token = text.split('access_token=')?.[1]?.split('&token_type')?.[0]
|
|
if (token) {
|
|
if (token.endsWith('/')) token = token.slice(0, -1)
|
|
handleToken(token)
|
|
}
|
|
} else if (text.includes("code=") && text.includes("&state")) { // is a MyAnimeList authorization
|
|
let code = line.split('code=')[1].split('&state')[0]
|
|
let state = line.split('&state=')[1]
|
|
if (code && state) {
|
|
if (code.endsWith('/')) code = code.slice(0, -1)
|
|
if (state.endsWith('/')) state = state.slice(0, -1)
|
|
if (state.includes('%')) state = decodeURIComponent(state)
|
|
handleMalToken(code, state)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
})
|
|
IPC.on('altoken', handleToken)
|
|
async function handleToken (token) {
|
|
const { anilistClient} = await import('./anilist.js')
|
|
const viewer = await anilistClient.viewer({token})
|
|
if (!viewer.data?.Viewer) {
|
|
toast.error('Failed to sign in with AniList. Please try again.', {description: JSON.stringify(viewer)})
|
|
debug(`Failed to sign in with AniList: ${JSON.stringify(viewer)}`)
|
|
return
|
|
}
|
|
const lists = viewer?.data?.Viewer?.mediaListOptions?.animeList?.customLists || []
|
|
if (!lists.includes('Watched using Migu')) {
|
|
await anilistClient.customList({lists})
|
|
}
|
|
swapProfiles({token, viewer})
|
|
location.reload()
|
|
}
|
|
|
|
IPC.on('maltoken', handleMalToken)
|
|
async function handleMalToken (code, state) {
|
|
const { clientID, malClient } = await import('./myanimelist.js')
|
|
if (!state || !code) {
|
|
toast.error('Failed to sign in with MyAnimeList. Please try again.')
|
|
debug(`Failed to get the state and code from MyAnimeList.`)
|
|
return
|
|
}
|
|
const response = await fetch('https://myanimelist.net/v1/oauth2/token', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
},
|
|
body: new URLSearchParams({
|
|
client_id: clientID,
|
|
grant_type: 'authorization_code',
|
|
code: code,
|
|
code_verifier: sessionStorage.getItem(state)
|
|
})
|
|
})
|
|
if (!response.ok) {
|
|
toast.error('Failed to sign in with MyAnimeList. Please try again.', { description: JSON.stringify(response.status) })
|
|
debug(`Failed to get MyAnimeList User Token: ${JSON.stringify(response)}`)
|
|
return
|
|
}
|
|
const oauth = await response.json()
|
|
const viewer = await malClient.viewer(oauth.access_token)
|
|
if (!viewer?.data?.Viewer?.id) {
|
|
toast.error('Failed to sign in with MyAnimeList. Please try again.', { description: JSON.stringify(viewer) })
|
|
debug(`Failed to sign in with MyAnimeList: ${JSON.stringify(viewer)}`)
|
|
return
|
|
} else if (!viewer?.data?.Viewer?.picture) {
|
|
viewer.data.Viewer.picture = 'https://cdn.myanimelist.net/images/kaomoji_mal_white.png' // set default image if user doesn't have an image.
|
|
}
|
|
swapProfiles({ token: oauth.access_token, refresh: oauth.refresh_token, viewer })
|
|
location.reload()
|
|
}
|
|
|
|
export async function refreshMalToken (token) {
|
|
const { clientID } = await import('./myanimelist.js')
|
|
const refresh = malToken?.token === token ? malToken.refresh : get(profiles).find(profile => profile.token === token)?.refresh
|
|
let response
|
|
if (!refresh || !(refresh.length > 0)) {
|
|
response = await fetch('https://myanimelist.net/v1/oauth2/token', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
},
|
|
body: new URLSearchParams({
|
|
client_id: clientID,
|
|
grant_type: 'refresh_token',
|
|
refresh_token: refresh
|
|
})
|
|
})
|
|
}
|
|
if (!refresh || !(refresh.length > 0) || !response.ok) {
|
|
toast.error('Failed to re-authenticate with MyAnimeList. You will need to log in again.', { description: JSON.stringify(response.status) })
|
|
debug(`Failed to refresh MyAnimeList User Token ${ !refresh || !(refresh.length > 0) ? 'as the refresh token could not be fetched!' : ': ' + JSON.stringify(response)}`)
|
|
if (malToken?.token === token) {
|
|
swapProfiles(null)
|
|
location.reload()
|
|
} else {
|
|
profiles.update(profiles =>
|
|
profiles.filter(profile => profile.token !== token)
|
|
)
|
|
}
|
|
return
|
|
}
|
|
const oauth = await response.json()
|
|
if (malToken?.token === token) {
|
|
const viewer = malToken.viewer
|
|
malToken = { token: oauth.access_token, refresh: oauth.refresh_token, viewer: viewer }
|
|
localStorage.setItem('MALviewer', JSON.stringify({ token: oauth.access_token, refresh: oauth.refresh_token, viewer }))
|
|
} else {
|
|
profiles.update(profiles =>
|
|
profiles.map(profile => {
|
|
if (profile.token === token) {
|
|
return { ...profile, token: oauth.access_token, refresh: oauth.refresh_token }
|
|
}
|
|
return profile
|
|
})
|
|
)
|
|
}
|
|
return oauth
|
|
}
|
|
|
|
export function swapProfiles(profile) {
|
|
const currentProfile = isAuthorized()
|
|
const newProfile = profile !== null && !get(profiles).some(p => p.viewer?.data?.Viewer?.id === currentProfile?.viewer?.data?.Viewer?.id)
|
|
|
|
if (currentProfile) {
|
|
const torrent = localStorage.getItem('torrent')
|
|
const lastFinished = localStorage.getItem('lastFinished')
|
|
const settings = localStorage.getItem('settings')
|
|
if (torrent) currentProfile.viewer.data.Viewer.torrent = torrent
|
|
if (lastFinished) currentProfile.viewer.data.Viewer.lastFinished = lastFinished
|
|
if (settings) currentProfile.viewer.data.Viewer.settings = settings
|
|
if (newProfile) profiles.update(currentProfiles => [currentProfile, ...currentProfiles])
|
|
}
|
|
localStorage.removeItem(alToken ? 'ALviewer' : 'MALviewer')
|
|
|
|
if (profile === null && get(profiles).length > 0) {
|
|
let firstProfile
|
|
profiles.update(profiles => {
|
|
firstProfile = profiles[0]
|
|
setViewer(firstProfile)
|
|
return profiles.slice(1)
|
|
})
|
|
} else if (profile !== null) {
|
|
setViewer(profile)
|
|
profiles.update(profiles =>
|
|
profiles.filter(p => p.viewer?.data?.Viewer?.id !== profile.viewer?.data?.Viewer?.id)
|
|
)
|
|
} else {
|
|
alToken = null
|
|
malToken = null
|
|
}
|
|
}
|
|
|
|
function setViewer (profile) {
|
|
const { torrent, lastFinished, settings } = profile?.viewer?.data?.Viewer
|
|
if (torrent) {
|
|
localStorage.setItem('torrent', torrent)
|
|
} else if (isAuthorized()) {
|
|
localStorage.removeItem('torrent')
|
|
}
|
|
if (lastFinished) {
|
|
localStorage.setItem('lastFinished', lastFinished)
|
|
} else if (isAuthorized()) {
|
|
localStorage.removeItem('lastFinished')
|
|
}
|
|
if (settings) {
|
|
localStorage.setItem('settings', settings)
|
|
} else if (isAuthorized()) {
|
|
localStorage.setItem('settings', writable({ ...defaults, ...scopedDefaults}))
|
|
}
|
|
if (profile?.viewer?.data?.Viewer?.avatar) {
|
|
alToken = profile
|
|
malToken = null
|
|
} else {
|
|
malToken = profile
|
|
alToken = null
|
|
}
|
|
localStorage.setItem(profile.viewer?.data?.Viewer?.avatar ? 'ALviewer' : 'MALviewer', JSON.stringify(profile))
|
|
}
|