mirror of
https://github.com/NoCrypt/migu.git
synced 2026-04-18 23:22:05 +00:00
feat: Profiles
This commit is contained in:
parent
d576ccded4
commit
76e79e6eaa
14 changed files with 481 additions and 262 deletions
|
|
@ -24,8 +24,7 @@
|
|||
import TorrentModal from './views/TorrentSearch/TorrentModal.svelte'
|
||||
import Menubar from './components/Menubar.svelte'
|
||||
import { Toaster } from 'svelte-sonner'
|
||||
import Login from './components/Login.svelte'
|
||||
import Logout from './components/Logout.svelte'
|
||||
import Profiles from './components/Profiles.svelte'
|
||||
import Navbar from './components/Navbar.svelte'
|
||||
|
||||
setContext('view', view)
|
||||
|
|
@ -33,8 +32,7 @@
|
|||
|
||||
<div class='page-wrapper with-transitions bg-dark position-relative' data-sidebar-type='overlayed-all'>
|
||||
<Menubar bind:page={$page} />
|
||||
<Login />
|
||||
<Logout />
|
||||
<Profiles />
|
||||
<Sidebar bind:page={$page} />
|
||||
<div class='overflow-hidden content-wrapper h-full'>
|
||||
<Toaster visibleToasts={6} position='top-right' theme='dark' richColors duration={10000} closeButton />
|
||||
|
|
|
|||
|
|
@ -1,103 +0,0 @@
|
|||
<script context='module'>
|
||||
import { click } from '@/modules/click.js'
|
||||
import { writable } from 'simple-store-svelte'
|
||||
import { platformMap } from '@/views/Settings/Settings.svelte'
|
||||
import { clientID } from '@/modules/myanimelist.js'
|
||||
import { toast } from 'svelte-sonner'
|
||||
import IPC from '@/modules/ipc.js'
|
||||
|
||||
export const login = writable(false)
|
||||
|
||||
function confirmAnilist () {
|
||||
IPC.emit('open', 'https://anilist.co/api/v2/oauth/authorize?client_id=4254&response_type=token') // Change redirect_url to miru://auth
|
||||
supportNotify()
|
||||
}
|
||||
|
||||
function confirmMAL () {
|
||||
const state = generateRandomString(10)
|
||||
const challenge = generateRandomString(50)
|
||||
sessionStorage.setItem(state, challenge)
|
||||
IPC.emit('open', `https://myanimelist.net/v1/oauth2/authorize?response_type=code&client_id=${clientID}&state=${state}&code_challenge=${challenge}&code_challenge_method=plain`) // Change redirect_url to miru://malauth
|
||||
supportNotify()
|
||||
}
|
||||
|
||||
function supportNotify() {
|
||||
if (platformMap[window.version.platform] === 'Linux') {
|
||||
toast('Support Notification', {
|
||||
description: "If your linux distribution doesn't support custom protocol handlers, you can simply paste the full URL into the app.",
|
||||
duration: 300000
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function generateRandomString(length) {
|
||||
let text = ''
|
||||
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~'
|
||||
for (let i = 0; i < length; i++) {
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length))
|
||||
}
|
||||
return text
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
let modal
|
||||
function close () {
|
||||
$login = false
|
||||
}
|
||||
function checkClose ({ keyCode }) {
|
||||
if (keyCode === 27) close()
|
||||
}
|
||||
$: $login && modal?.focus()
|
||||
</script>
|
||||
|
||||
<div class='modal z-101' class:show={$login}>
|
||||
{#if $login}
|
||||
<div class='modal-dialog' on:pointerup|self={close} on:keydown={checkClose} tabindex='-1' role='button' bind:this={modal}>
|
||||
<div class='modal-content d-flex justify-content-center flex-column'>
|
||||
<button class='close pointer z-30 top-20 right-0 position-absolute' type='button' use:click={close}> × </button>
|
||||
<h5 class='modal-title'>Log In</h5>
|
||||
<div class='modal-buttons d-flex flex-column align-items-center'>
|
||||
<div class='modal-button mb-10 d-flex justify-content-center flex-row'>
|
||||
<img src='./anilist_logo.png' class='al-logo position-absolute rounded pointer-events-none' alt='logo' />
|
||||
<button class='btn anilist w-150' type='button' on:click={confirmAnilist}></button>
|
||||
</div>
|
||||
<div class='modal-button d-flex justify-content-center flex-row'>
|
||||
<img src='./myanimelist_logo.png' class='mal-logo position-absolute rounded pointer-events-none' alt='logo' />
|
||||
<button class='btn myanimelist w-150' type='button' on:click={confirmMAL}></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.close {
|
||||
top: 4rem !important;
|
||||
left: unset !important;
|
||||
right: 2.5rem !important;
|
||||
}
|
||||
|
||||
.mal-logo {
|
||||
height: 2rem;
|
||||
margin-top: 0.8rem;
|
||||
}
|
||||
.al-logo {
|
||||
height: 1.6rem;
|
||||
margin-top: 0.82rem;
|
||||
}
|
||||
|
||||
.anilist {
|
||||
background-color: #283342 !important;
|
||||
}
|
||||
.myanimelist {
|
||||
background-color: #2C51A2 !important;
|
||||
}
|
||||
.anilist:hover {
|
||||
background-color: #46536c !important;
|
||||
}
|
||||
.myanimelist:hover {
|
||||
background-color: #2861d6 !important;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
<script context='module'>
|
||||
import { click } from '@/modules/click.js'
|
||||
import { writable } from 'simple-store-svelte'
|
||||
import Helper from '@/modules/helper.js'
|
||||
|
||||
export const logout = writable(false)
|
||||
|
||||
function confirm () {
|
||||
localStorage.removeItem('ALviewer')
|
||||
localStorage.removeItem('MALviewer')
|
||||
location.hash = ''
|
||||
location.reload()
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
let modal
|
||||
function close () {
|
||||
$logout = false
|
||||
}
|
||||
function checkClose ({ keyCode }) {
|
||||
if (keyCode === 27) close()
|
||||
}
|
||||
$: $logout && modal?.focus()
|
||||
</script>
|
||||
|
||||
<div class='modal z-101' class:show={$logout}>
|
||||
{#if $logout}
|
||||
<div class='modal-dialog' on:pointerup|self={close} on:keydown={checkClose} tabindex='-1' role='button' bind:this={modal}>
|
||||
<div class='modal-content d-flex justify-content-center flex-column'>
|
||||
<button class='close pointer z-30 top-20 right-0 position-absolute' type='button' use:click={close}> × </button>
|
||||
<h5 class='modal-title'>Log Out</h5>
|
||||
<p>
|
||||
Are You Sure You Want To Sign Out? Your Progress Will No Longer Be Tracked On <u>{Helper.isAniAuth() ? 'AniList' : 'MyAnimeList'}</u>.
|
||||
</p>
|
||||
<div class='text-right mt-20'>
|
||||
<button class='btn mr-5' type='button' on:click={close}>Cancel</button>
|
||||
<button class='btn btn-danger' type='button' on:click={confirm}>Sign Out</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.close {
|
||||
top: 4rem !important;
|
||||
left: unset !important;
|
||||
right: 2.5rem !important;
|
||||
}
|
||||
</style>
|
||||
234
common/components/Profiles.svelte
Normal file
234
common/components/Profiles.svelte
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
<script context='module'>
|
||||
import { generateRandomString } from "@/modules/util.js"
|
||||
import { get, writable } from 'simple-store-svelte'
|
||||
import { swapProfiles, alToken, malToken } from '@/modules/settings.js'
|
||||
import { platformMap } from '@/views/Settings/Settings.svelte'
|
||||
import { clientID } from "@/modules/myanimelist.js"
|
||||
import { click } from '@/modules/click.js'
|
||||
import { toast } from 'svelte-sonner'
|
||||
import IPC from "@/modules/ipc"
|
||||
|
||||
export const profileView = writable(false)
|
||||
const profileAdd = writable(false)
|
||||
const currentProfile = writable(alToken || malToken)
|
||||
const profiles = writable(JSON.parse(localStorage.getItem('profiles')) || [])
|
||||
|
||||
function isAniProfile (profile) {
|
||||
return profile.viewer?.data?.Viewer?.avatar
|
||||
}
|
||||
|
||||
function currentLogout () {
|
||||
swapProfiles(null)
|
||||
location.reload()
|
||||
}
|
||||
|
||||
function dropProfile (profile) {
|
||||
profiles.update(profiles => {
|
||||
const updatedProfiles = profiles.filter(p => p.viewer.data.Viewer.id !== profile.viewer?.data?.Viewer.id)
|
||||
localStorage.setItem('profiles', JSON.stringify(updatedProfiles))
|
||||
return updatedProfiles
|
||||
})
|
||||
}
|
||||
|
||||
function switchProfile (profile) {
|
||||
swapProfiles(profile)
|
||||
location.reload()
|
||||
}
|
||||
|
||||
function toggleSync(profile) {
|
||||
const mainProfile = get(currentProfile)
|
||||
if (profile.viewer.data.Viewer.id === mainProfile.viewer.data.Viewer.id) {
|
||||
mainProfile.viewer.data.Viewer.sync = !mainProfile.viewer.data.Viewer.sync
|
||||
localStorage.setItem(isAniProfile(mainProfile) ? 'ALviewer' : 'MALviewer', JSON.stringify(mainProfile))
|
||||
} else {
|
||||
profiles.update(profiles => {
|
||||
const updatedProfiles = profiles.map(p => {
|
||||
if (p.viewer.data.Viewer.id === profile.viewer.data.Viewer.id) {
|
||||
p.viewer.data.Viewer.sync = !p.viewer.data.Viewer.sync
|
||||
}
|
||||
return p
|
||||
})
|
||||
localStorage.setItem('profiles', JSON.stringify(updatedProfiles))
|
||||
return updatedProfiles
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function confirmAnilist () {
|
||||
IPC.emit('open', 'https://anilist.co/api/v2/oauth/authorize?client_id=4254&response_type=token') // Change redirect_url to miru://auth
|
||||
supportNotify()
|
||||
}
|
||||
|
||||
function confirmMAL () {
|
||||
const state = generateRandomString(10)
|
||||
const challenge = generateRandomString(50)
|
||||
sessionStorage.setItem(state, challenge)
|
||||
IPC.emit('open', `https://myanimelist.net/v1/oauth2/authorize?response_type=code&client_id=${clientID}&state=${state}&code_challenge=${challenge}&code_challenge_method=plain`) // Change redirect_url to miru://malauth
|
||||
supportNotify()
|
||||
}
|
||||
|
||||
function supportNotify() {
|
||||
if (platformMap[window.version.platform] === 'Linux') {
|
||||
toast('Support Notification', {
|
||||
description: "If your linux distribution doesn't support custom protocol handlers, you can simply paste the full URL into the app.",
|
||||
duration: 300000
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
let modal
|
||||
function close () {
|
||||
$profileView = false
|
||||
$profileAdd = false
|
||||
}
|
||||
function checkClose ({ keyCode }) {
|
||||
if (keyCode === 27) close()
|
||||
}
|
||||
$: $profileView && modal?.focus()
|
||||
</script>
|
||||
|
||||
<div class='modal z-101' class:show={$profileView}>
|
||||
{#if $profileView}
|
||||
<div class='modal-dialog' on:pointerup|self={close} on:keydown={checkClose} tabindex='-1' role='button' bind:this={modal}>
|
||||
<div class='modal-content w-auto mw-400 d-flex justify-content-center flex-column'>
|
||||
<button class='close pointer z-30 top-20 right-0 position-absolute' type='button' use:click={close}> × </button>
|
||||
<div class='d-flex flex-column align-items-center'>
|
||||
{#if $currentProfile}
|
||||
<img class='h-150 rounded-circle' src={$currentProfile.viewer.data.Viewer.avatar?.medium || $currentProfile.viewer.data.Viewer.picture} alt='Current Profile' title='Current Profile'>
|
||||
<img class='h-3 auth-icon rounded-circle' src={isAniProfile($currentProfile) ? './anilist_icon.png' : './myanimelist_icon.png'} alt={isAniProfile($currentProfile) ? 'Logged in with AniList' : 'Logged in with MyAnimeList'} title={isAniProfile($currentProfile) ? 'Logged in with AniList' : 'Logged in with MyAnimeList'}>
|
||||
<p class='font-size-18 font-weight-bold'>{$currentProfile.viewer.data.Viewer.name}</p>
|
||||
{/if}
|
||||
</div>
|
||||
{#if $profiles.length > 0}
|
||||
<div class='info box pointer border-0 rounded-top-10 pt-10 pb-10 d-flex align-items-center justify-content-center text-center font-weight-bold'>
|
||||
Other Profiles
|
||||
</div>
|
||||
{/if}
|
||||
<div class='d-flex flex-column align-items-start'>
|
||||
{#each $profiles as profile}
|
||||
<button type='button' class='profile-item box text-left pointer border-0 d-flex align-items-center justify-content-between position-relative flex-wrap' on:click={() => switchProfile(profile)}>
|
||||
<div class='d-flex align-items-center flex-wrap'>
|
||||
<img class='h-50 ml-10 mt-5 mb-5 mr-10 rounded-circle bg-transparent' src={profile.viewer.data.Viewer.avatar?.medium || profile.viewer.data.Viewer.picture} alt={profile.viewer.data.Viewer.name}>
|
||||
<img class='ml-5 auth-icon rounded-circle' src={isAniProfile(profile) ? './anilist_icon.png' : './myanimelist_icon.png'} alt={isAniProfile(profile) ? 'Logged in with AniList' : 'Logged in with MyAnimeList'} title={isAniProfile(profile) ? 'Logged in with AniList' : 'Logged in with MyAnimeList'}>
|
||||
<p class='text-wrap'>{profile.viewer.data.Viewer.name}</p>
|
||||
</div>
|
||||
<div class='controls d-flex align-items-center flex-wrap ml-10'>
|
||||
<button type='button' class='custom-switch bg-transparent border-0' title='Sync List Entries' on:click|stopPropagation>
|
||||
<input type='checkbox' id='sync-{profile.viewer.data.Viewer.id}' bind:checked={profile.viewer.data.Viewer.sync} on:click={() => toggleSync(profile)} />
|
||||
<label for='sync-{profile.viewer.data.Viewer.id}'><br/></label>
|
||||
</button>
|
||||
<button type='button' class='button logout pt-5 pb-5 pl-5 pr-5 material-symbols-outlined rounded bg-transparent border-0' title='Logout' on:click|stopPropagation={() => dropProfile(profile)}>
|
||||
logout
|
||||
</button>
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
{#if ($profileAdd || (!$currentProfile && $profiles.length <= 0)) && $profiles.length < 5}
|
||||
<div class='modal-buttons box pointer border-0 info d-flex flex-column {$profiles.length > 0 ? "" : "rounded-top-10"} {$currentProfile || $profiles.length > 0 ? "align-items-center" : "bg-transparent"}'>
|
||||
{#if !$currentProfile && $profiles.length <= 0}
|
||||
<h5 class='modal-title'>Log In</h5>
|
||||
{/if}
|
||||
<div class='modal-button mb-10 d-flex justify-content-center flex-row {$currentProfile || $profiles.length > 0 ? "mt-10" : ""}'>
|
||||
<img class='al-logo position-absolute rounded pointer-events-none' src='./anilist_logo.png' alt='logo' />
|
||||
<button class='btn anilist w-150' type='button' on:click={confirmAnilist}></button>
|
||||
</div>
|
||||
<div class='modal-button mb-10 d-flex justify-content-center flex-row'>
|
||||
<img class='mal-logo position-absolute rounded pointer-events-none' src='./myanimelist_logo.png' alt='logo' />
|
||||
<button class='btn myanimelist w-150' type='button' on:click={confirmMAL}></button>
|
||||
</div>
|
||||
</div>
|
||||
{:else if $profiles.length < 5}
|
||||
<button type='button' class='box pointer border-0 pt-10 pb-10 d-flex align-items-center justify-content-center text-center {$profiles.length > 0 && $currentProfile ? "" : !$currentProfile ? "rounded-bottom-10" : "rounded-top-10"}' on:click={() => { $profileAdd = true }}>
|
||||
<div class='material-symbols-outlined rounded mr-10'>
|
||||
add
|
||||
</div>
|
||||
<div class='mt-2'>
|
||||
Add Profile
|
||||
</div>
|
||||
</button>
|
||||
{/if}
|
||||
{#if $currentProfile}
|
||||
{#if $profiles.length > 0}
|
||||
<div class='box pointer border-0 pt-10 pb-10 d-flex align-items-center justify-content-center text-center'>
|
||||
<div class='custom-switch' title='Must be enabled to sync list entries with other sync enabled profiles.'>
|
||||
<input type='checkbox' id='sync-{$currentProfile.viewer.data.Viewer.id}' bind:checked={$currentProfile.viewer.data.Viewer.sync} on:click={() => toggleSync($currentProfile)} />
|
||||
<label for='sync-{$currentProfile.viewer.data.Viewer.id}'>Sync Entries</label>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<button type='button' class='box pointer border-0 rounded-bottom-10 pt-10 pb-10 d-flex align-items-center justify-content-center text-center' on:click={currentLogout}>
|
||||
<div class='material-symbols-outlined bg-transparent mr-10'>
|
||||
logout
|
||||
</div>
|
||||
<div class='mt-2'>
|
||||
Sign Out
|
||||
</div>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.close {
|
||||
top: 4rem !important;
|
||||
left: unset !important;
|
||||
right: 2.5rem !important;
|
||||
}
|
||||
.logout:hover {
|
||||
background: #393838 !important;
|
||||
}
|
||||
.h-3 {
|
||||
height: 3rem !important;
|
||||
}
|
||||
.mt-2 {
|
||||
margin-top: .4rem;
|
||||
}
|
||||
.mw-400 {
|
||||
min-width: 35rem;
|
||||
}
|
||||
.rounded-top-10 {
|
||||
border-radius: 3rem 3rem 0 0;
|
||||
}
|
||||
.rounded-bottom-10 {
|
||||
border-radius: 0 0 3rem 3rem;
|
||||
}
|
||||
.auth-icon {
|
||||
position: absolute;
|
||||
height: 2rem;
|
||||
margin-right: 15rem;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
.box:hover:not(.info) {
|
||||
background: #272727;
|
||||
}
|
||||
.box {
|
||||
background: #0e0e0e;
|
||||
width: 100%;
|
||||
margin-bottom: .3rem;
|
||||
}
|
||||
|
||||
.mal-logo {
|
||||
height: 2rem;
|
||||
margin-top: 0.8rem;
|
||||
}
|
||||
.al-logo {
|
||||
height: 1.6rem;
|
||||
margin-top: 0.82rem;
|
||||
}
|
||||
.anilist {
|
||||
background-color: #283342 !important;
|
||||
}
|
||||
.myanimelist {
|
||||
background-color: #2C51A2 !important;
|
||||
}
|
||||
.anilist:hover {
|
||||
background-color: #46536c !important;
|
||||
}
|
||||
.myanimelist:hover {
|
||||
background-color: #2861d6 !important;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -4,8 +4,7 @@
|
|||
import { settings } from '@/modules/settings.js'
|
||||
import { toast } from 'svelte-sonner'
|
||||
import { click } from '@/modules/click.js'
|
||||
import { login } from './Login.svelte'
|
||||
import { logout } from './Logout.svelte'
|
||||
import { profileView } from './Profiles.svelte'
|
||||
import Helper from '@/modules/helper.js'
|
||||
import IPC from '@/modules/ipc.js'
|
||||
|
||||
|
|
@ -44,11 +43,7 @@
|
|||
let links = [
|
||||
{
|
||||
click: () => {
|
||||
if (Helper.getUser()) {
|
||||
$logout = true
|
||||
} else {
|
||||
$login = true
|
||||
}
|
||||
$profileView = true
|
||||
},
|
||||
icon: 'login',
|
||||
text: 'Login',
|
||||
|
|
@ -114,7 +109,7 @@
|
|||
]
|
||||
if (Helper.getUser()) {
|
||||
links[0].image = Helper.getUserAvatar()
|
||||
links[0].text = 'Logout'
|
||||
links[0].text = 'Profiles'
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -247,8 +247,8 @@ class AnilistClient {
|
|||
}
|
||||
})
|
||||
}
|
||||
// @ts-ignore
|
||||
if (alToken?.token) options.headers.Authorization = alToken.token
|
||||
if (variables?.token) options.headers.Authorization = variables.token
|
||||
else if (alToken?.token) options.headers.Authorization = alToken.token
|
||||
|
||||
return this.handleRequest(options)
|
||||
}
|
||||
|
|
@ -486,9 +486,7 @@ class AnilistClient {
|
|||
}`
|
||||
|
||||
const res = await this.alRequest(query, variables)
|
||||
|
||||
await this.updateCache(res.data.MediaListCollection.lists.flatMap(list => list.entries.map(entry => entry.media)))
|
||||
|
||||
if (!variables.token) await this.updateCache(res.data.MediaListCollection.lists.flatMap(list => list.entries.map(entry => entry.media)))
|
||||
return res
|
||||
}
|
||||
|
||||
|
|
@ -627,7 +625,7 @@ class AnilistClient {
|
|||
return this.alRequest(query, variables)
|
||||
}
|
||||
|
||||
entry (variables) {
|
||||
async entry (variables) {
|
||||
const query = /* js */`
|
||||
mutation($lists: [String], $id: Int, $status: MediaListStatus, $episode: Int, $repeat: Int, $score: Int, $startedAt: FuzzyDateInput, $completedAt: FuzzyDateInput) {
|
||||
SaveMediaListEntry(mediaId: $id, status: $status, progress: $episode, repeat: $repeat, scoreRaw: $score, customLists: $lists, startedAt: $startedAt, completedAt: $completedAt) {
|
||||
|
|
@ -649,18 +647,21 @@ class AnilistClient {
|
|||
}
|
||||
}`
|
||||
|
||||
return this.alRequest(query, variables)
|
||||
const res = await this.alRequest(query, variables)
|
||||
if (!variables.token) this.userLists.value = this.getUserLists({sort: 'UPDATED_TIME_DESC'})
|
||||
return res
|
||||
}
|
||||
|
||||
delete (variables) {
|
||||
async delete (variables) {
|
||||
const query = /* js */`
|
||||
mutation($id: Int) {
|
||||
DeleteMediaListEntry(id: $id) {
|
||||
deleted
|
||||
}
|
||||
}`
|
||||
|
||||
return this.alRequest(query, variables)
|
||||
const res = await this.alRequest(query, variables)
|
||||
if (!variables.token) this.userLists.value = this.getUserLists({sort: 'UPDATED_TIME_DESC'})
|
||||
return res
|
||||
}
|
||||
|
||||
favourite (variables) {
|
||||
|
|
|
|||
|
|
@ -132,13 +132,30 @@ export default class Helper {
|
|||
}
|
||||
|
||||
static async entry(media, variables) {
|
||||
let res = await this.getClient().entry(variables)
|
||||
media.mediaListEntry = res.data.SaveMediaListEntry
|
||||
let res
|
||||
if (!variables.token) {
|
||||
res = await this.getClient().entry(variables)
|
||||
media.mediaListEntry = res?.data?.SaveMediaListEntry
|
||||
} else {
|
||||
if (variables.anilist) {
|
||||
res = await anilistClient.entry(variables)
|
||||
} else {
|
||||
res = await malClient.entry(variables)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
static async delete(media) {
|
||||
return await this.getClient().delete((this.isAniAuth() ? {id: media.mediaListEntry.id} : {idMal: media.idMal}))
|
||||
static async delete(variables) {
|
||||
if (!variables.token) {
|
||||
return await this.getClient().delete(variables)
|
||||
} else {
|
||||
if (variables.anilist) {
|
||||
return await anilistClient.delete(variables)
|
||||
} else {
|
||||
return await malClient.delete(variables)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static matchTitle(media, phrase, keys) {
|
||||
|
|
@ -230,27 +247,50 @@ export default class Helper {
|
|||
Object.assign(variables, this.getFuzzyDate(media, status))
|
||||
|
||||
let res
|
||||
const description = `Title: ${anilistClient.title(media)}\nStatus: ${this.statusName[variables.status]}\nEpisode: ${videoEpisode} / ${media.episodes ? media.episodes : '?'}`
|
||||
if (this.isAniAuth()) {
|
||||
res = await anilistClient.alEntry(lists, variables)
|
||||
} else if (this.isMalAuth()) {
|
||||
res = await malClient.malEntry(media, variables)
|
||||
}
|
||||
this.listToast(res, description, false)
|
||||
|
||||
const description = `Title: ${anilistClient.title(media)}\nStatus: ${this.statusName[media.mediaListEntry.status]}\nEpisode: ${videoEpisode} / ${media.episodes ? media.episodes : '?'}`
|
||||
if (res?.data?.mediaListEntry || res?.data?.SaveMediaListEntry) {
|
||||
console.log('List Updated: ' + description)
|
||||
if (this.getUser().sync) { // handle profile syncing
|
||||
const mediaId = media.id
|
||||
const profiles = JSON.parse(localStorage.getItem('profiles')) || []
|
||||
for (const profile of profiles) {
|
||||
if (profile.viewer?.data?.Viewer.sync) {
|
||||
let res
|
||||
if (profile.viewer?.data?.Viewer?.avatar) {
|
||||
const currentLists = (await anilistClient.getUserLists({userID: profile.viewer.data.Viewer.id, token: profile.token}))?.data?.MediaListCollection?.lists?.flatMap(list => list.entries).find(({ media }) => media.id === mediaId)?.media?.mediaListEntry?.customLists?.filter(list => list.enabled).map(list => list.name) || []
|
||||
res = await anilistClient.alEntry(currentLists, {...variables, token: profile.token})
|
||||
} else {
|
||||
res = await malClient.malEntry(media, {...variables, token: profile.token})
|
||||
}
|
||||
this.listToast(res, description, profile)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static listToast(res, description, profile){
|
||||
const who = (profile ? ' for ' + profile.viewer.data.Viewer.name + (profile.viewer?.data?.Viewer?.avatar ? ' (AniList)' : ' (MyAnimeList)') : '')
|
||||
if (res?.data?.mediaListEntry || res?.data?.SaveMediaListEntry) {
|
||||
console.log('List Updated' + who + ':\n' + description)
|
||||
if (!profile) {
|
||||
toast.success('List Updated', {
|
||||
description,
|
||||
duration: 6000
|
||||
})
|
||||
} else {
|
||||
const error = `\n${429} - ${codes[429]}`
|
||||
console.error('Failed to update user list with: ' + description + error)
|
||||
toast.error('Failed to Update List', {
|
||||
description: description + error,
|
||||
duration: 9000
|
||||
})
|
||||
}
|
||||
} else {
|
||||
const error = `\n${429} - ${codes[429]}`
|
||||
console.error('Failed to update user list' + who + ' with:\n' + description + error)
|
||||
toast.error('Failed to Update List' + who, {
|
||||
description: description + error,
|
||||
duration: 9000
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { writable } from 'simple-store-svelte'
|
||||
|
||||
import { malToken, refreshMalToken } from '@/modules/settings.js'
|
||||
import { codes } from "@/modules/anilist";
|
||||
import { codes } from "@/modules/anilist"
|
||||
import { toast } from 'svelte-sonner'
|
||||
import Helper from '@/modules/helper.js'
|
||||
|
||||
|
|
@ -90,14 +90,14 @@ class MALClient {
|
|||
switch (res.error) {
|
||||
case 'forbidden':
|
||||
case 'invalid_token':
|
||||
if (await refreshMalToken()) { // refresh authorization token as it typically expires every 31 days.
|
||||
return this.handleRequest(query, options);
|
||||
if (await refreshMalToken(query.token ? query.token : this.userID.token)) { // refresh authorization token as it typically expires every 31 days.
|
||||
return this.handleRequest(query, options)
|
||||
}
|
||||
throw new Error("NotAuthenticatedError: " + res.message ?? res.error);
|
||||
throw new Error("NotAuthenticatedError: " + res.message ?? res.error)
|
||||
case 'invalid_content':
|
||||
throw new Error(`This Entry is currently pending approval. It can't be saved to mal for now`);
|
||||
throw new Error(`This Entry is currently pending approval. It can't be saved to mal for now`)
|
||||
default:
|
||||
throw new Error(res.message ?? res.error);
|
||||
throw new Error(res.message ?? res.error)
|
||||
}
|
||||
} else if (!res || res.status !== 404) {
|
||||
throw e
|
||||
|
|
@ -127,7 +127,7 @@ class MALClient {
|
|||
async malEntry (media, variables) {
|
||||
variables.idMal = media.idMal
|
||||
const res = await malClient.entry(variables)
|
||||
media.mediaListEntry = res.data.SaveMediaListEntry
|
||||
if (!variables.token) media.mediaListEntry = res.data.SaveMediaListEntry
|
||||
return res
|
||||
}
|
||||
|
||||
|
|
@ -158,19 +158,19 @@ class MALClient {
|
|||
hasNextPage = false
|
||||
} else {
|
||||
offset += limit
|
||||
await new Promise(resolve => setTimeout(resolve, 1000)); // 1-second delay to prevent too many consecutive requests ip block.
|
||||
await new Promise(resolve => setTimeout(resolve, 1000)) // 1-second delay to prevent too many consecutive requests ip block.
|
||||
}
|
||||
}
|
||||
|
||||
// Custom sorting based on original variables.sort value
|
||||
if (variables.originalSort === 'list_start_date_nan') {
|
||||
allMediaList.sort((a, b) => {
|
||||
return new Date(b.node.my_list_status.start_date) - new Date(a.node.my_list_status.start_date);
|
||||
});
|
||||
return new Date(b.node.my_list_status.start_date) - new Date(a.node.my_list_status.start_date)
|
||||
})
|
||||
} else if (variables.originalSort === 'list_finish_date_nan') {
|
||||
allMediaList.sort((a, b) => {
|
||||
return new Date(b.node.my_list_status.finish_date) - new Date(a.node.my_list_status.finish_date);
|
||||
});
|
||||
return new Date(b.node.my_list_status.finish_date) - new Date(a.node.my_list_status.finish_date)
|
||||
})
|
||||
} else if (variables.originalSort === 'list_progress_nan') {
|
||||
allMediaList.sort((a, b) => {
|
||||
return b.node.my_list_status.num_episodes_watched - a.node.my_list_status.num_episodes_watched
|
||||
|
|
@ -201,9 +201,10 @@ class MALClient {
|
|||
async entry (variables) {
|
||||
const query = {
|
||||
type: 'PUT',
|
||||
path: `anime/${variables.idMal}/my_list_status`
|
||||
path: `anime/${variables.idMal}/my_list_status`,
|
||||
token: variables.token
|
||||
}
|
||||
const padNumber = (num) => num !== undefined && num !== null ? String(num).padStart(2, '0') : null;
|
||||
const padNumber = (num) => num !== undefined && num !== null ? String(num).padStart(2, '0') : null
|
||||
const start_date = variables.startedAt?.year && variables.startedAt.month && variables.startedAt.day ? `${variables.startedAt.year}-${padNumber(variables.startedAt.month)}-${padNumber(variables.startedAt.day)}` : null
|
||||
const finish_date = variables.completedAt?.year && variables.completedAt.month && variables.completedAt.day ? `${variables.completedAt.year}-${padNumber(variables.completedAt.month)}-${padNumber(variables.completedAt.day)}` : null
|
||||
const updateData = {
|
||||
|
|
@ -219,9 +220,9 @@ class MALClient {
|
|||
if (finish_date) {
|
||||
updateData.finish_date = finish_date
|
||||
}
|
||||
const res = await this.malRequest(query, updateData)
|
||||
this.userLists.value = this.getUserLists({ sort: 'list_updated_at' })
|
||||
|
||||
const res = await this.malRequest(query, updateData)
|
||||
if (!variables.token) this.userLists.value = this.getUserLists({ sort: 'list_updated_at' })
|
||||
return res ? {
|
||||
data: {
|
||||
SaveMediaListEntry: {
|
||||
|
|
@ -241,10 +242,11 @@ class MALClient {
|
|||
async delete (variables) {
|
||||
const query = {
|
||||
type: 'DELETE',
|
||||
path: `anime/${variables.idMal}/my_list_status`
|
||||
path: `anime/${variables.idMal}/my_list_status`,
|
||||
token: variables.token
|
||||
}
|
||||
const res = await this.malRequest(query)
|
||||
this.userLists.value = this.getUserLists({ sort: 'list_updated_at' })
|
||||
if (!variables.token) this.userLists.value = this.getUserLists({ sort: 'list_updated_at' })
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,19 +66,18 @@ window.addEventListener('paste', ({ clipboardData }) => {
|
|||
})
|
||||
IPC.on('altoken', handleToken)
|
||||
async function handleToken (token) {
|
||||
alToken = { token, viewer: null }
|
||||
const { anilistClient } = await import('./anilist.js')
|
||||
const viewer = await anilistClient.viewer({ 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) })
|
||||
toast.error('Failed to sign in with AniList. Please try again.', {description: JSON.stringify(viewer)})
|
||||
console.error(viewer)
|
||||
return
|
||||
}
|
||||
const lists = viewer?.data?.Viewer?.mediaListOptions?.animeList?.customLists || []
|
||||
if (!lists.includes('Watched using Miru')) {
|
||||
await anilistClient.customList({ lists })
|
||||
await anilistClient.customList({lists})
|
||||
}
|
||||
localStorage.setItem('ALviewer', JSON.stringify({ token, viewer }))
|
||||
swapProfiles({token, viewer})
|
||||
location.reload()
|
||||
}
|
||||
|
||||
|
|
@ -108,7 +107,6 @@ async function handleMalToken (code, state) {
|
|||
return
|
||||
}
|
||||
const oauth = await response.json()
|
||||
malToken = { token: oauth.access_token, refresh:oauth.refresh_token, viewer: null }
|
||||
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) })
|
||||
|
|
@ -117,11 +115,11 @@ async function handleMalToken (code, state) {
|
|||
} 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.
|
||||
}
|
||||
localStorage.setItem('MALviewer', JSON.stringify({ token: oauth.access_token, refresh: oauth.refresh_token, viewer }))
|
||||
swapProfiles({ token: oauth.access_token, refresh: oauth.refresh_token, viewer })
|
||||
location.reload()
|
||||
}
|
||||
|
||||
export async function refreshMalToken () {
|
||||
export async function refreshMalToken (token) {
|
||||
const { clientID } = await import('./myanimelist.js')
|
||||
const response = await fetch('https://myanimelist.net/v1/oauth2/token', {
|
||||
method: 'POST',
|
||||
|
|
@ -137,12 +135,77 @@ export async function refreshMalToken () {
|
|||
if (!response.ok) {
|
||||
toast.error('Failed to re-authenticate with MyAnimeList. You will need to log in again.', { description: JSON.stringify(response.status) })
|
||||
console.error('Failed to refresh MyAnimeList User Token.', response)
|
||||
malToken = null
|
||||
localStorage.removeItem('MALviewer')
|
||||
if (malToken.token === token) {
|
||||
swapProfiles(null)
|
||||
} else {
|
||||
const profiles = JSON.parse(localStorage.getItem('profiles')) || []
|
||||
localStorage.setItem('profiles', JSON.stringify(profiles.filter(profile => profile.token !== token)))
|
||||
}
|
||||
return
|
||||
}
|
||||
const oauth = await response.json()
|
||||
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 }))
|
||||
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 {
|
||||
let profiles = JSON.parse(localStorage.getItem('profiles')) || []
|
||||
profiles = profiles.map(profile => {
|
||||
if (profile.token === token) {
|
||||
return {...profile, token: oauth.access_token, refresh: oauth.refresh_token}
|
||||
}
|
||||
return profile
|
||||
})
|
||||
localStorage.setItem('profiles', JSON.stringify(profiles))
|
||||
}
|
||||
}
|
||||
|
||||
export function swapProfiles(profile) {
|
||||
let profiles = JSON.parse(localStorage.getItem('profiles')) || []
|
||||
const currentProfile = isAuthorized()
|
||||
const newProfile = profile !== null && !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.unshift(currentProfile)
|
||||
}
|
||||
localStorage.removeItem(alToken ? 'ALviewer' : 'MALviewer')
|
||||
|
||||
if (profile === null && profiles.length > 0) {
|
||||
const firstProfile = profiles.shift()
|
||||
setViewer(firstProfile)
|
||||
} else if (profile !== null) {
|
||||
profiles = profiles.filter(p => p.viewer?.data?.Viewer?.id !== profile.viewer?.data?.Viewer?.id)
|
||||
setViewer(profile)
|
||||
} else {
|
||||
alToken = null
|
||||
malToken = null
|
||||
}
|
||||
|
||||
localStorage.setItem('profiles', JSON.stringify(profiles))
|
||||
}
|
||||
|
||||
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}))
|
||||
}
|
||||
localStorage.setItem(profile.viewer?.data?.Viewer?.avatar ? 'ALviewer' : 'MALviewer', JSON.stringify(profile))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,6 +94,15 @@ export function generateRandomHexCode (len) {
|
|||
return hexCode
|
||||
}
|
||||
|
||||
export function generateRandomString(length) {
|
||||
let string = ''
|
||||
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~'
|
||||
for (let i = 0; i < length; i++) {
|
||||
string += possible.charAt(Math.floor(Math.random() * possible.length))
|
||||
}
|
||||
return string
|
||||
}
|
||||
|
||||
export function matchPhrase(search, phrase, threshold) {
|
||||
if (!search) return false
|
||||
const normalizedSearch = search.toLowerCase().replace(/[^\w\s]/g, '')
|
||||
|
|
|
|||
BIN
common/public/anilist_icon.png
Normal file
BIN
common/public/anilist_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.3 KiB |
BIN
common/public/myanimelist_icon.png
Normal file
BIN
common/public/myanimelist_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 59 KiB |
|
|
@ -34,8 +34,7 @@
|
|||
import TorrentSettings from './TorrentSettings.svelte'
|
||||
import InterfaceSettings from './InterfaceSettings.svelte'
|
||||
import AppSettings from './AppSettings.svelte'
|
||||
import { login } from '@/components/Login.svelte'
|
||||
import { logout } from '@/components/Logout.svelte'
|
||||
import { profileView } from '@/components/Profiles.svelte'
|
||||
import smoothScroll from '@/modules/scroll.js'
|
||||
import Helper from '@/modules/helper.js'
|
||||
|
||||
|
|
@ -70,11 +69,7 @@
|
|||
}
|
||||
|
||||
function loginButton () {
|
||||
if (Helper.getUser()) {
|
||||
$logout = true
|
||||
} else {
|
||||
$login = true
|
||||
}
|
||||
$profileView = true
|
||||
}
|
||||
onDestroy(() => {
|
||||
IPC.off('path', pathListener)
|
||||
|
|
@ -109,7 +104,7 @@
|
|||
<span class='material-symbols-outlined rounded mr-10'>
|
||||
<img src={Helper.getUserAvatar()} class='h-30 rounded' alt='logo' />
|
||||
</span>
|
||||
<div class='font-size-16 login-image-text'>Logout</div>
|
||||
<div class='font-size-16 login-image-text'>Profiles</div>
|
||||
{:else}
|
||||
<span class='material-symbols-outlined font-size-24 pr-10 d-inline-flex justify-content-center align-items-center'>login</span>
|
||||
<div class='font-size-16'>Login</div>
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@
|
|||
if (state.save || state.delete) {
|
||||
await new Promise(resolve => setTimeout(resolve, 300)) // allows time for animation to play
|
||||
if (state.save) {
|
||||
await saveChanges()
|
||||
await saveEntry()
|
||||
} else if (state.delete) {
|
||||
await deleteEntry()
|
||||
}
|
||||
|
|
@ -55,29 +55,36 @@
|
|||
episode = 0
|
||||
status = 'NOT IN LIST'
|
||||
if (media.mediaListEntry) {
|
||||
const res = await Helper.delete(media)
|
||||
const res = await Helper.delete(Helper.isAniAuth() ? {id: media.mediaListEntry.id} : {idMal: media.idMal})
|
||||
const description = `${anilistClient.title(media)} has been deleted from your list.`
|
||||
if (res) {
|
||||
console.log('List Updated: ' + description)
|
||||
toast.warning('List Updated', {
|
||||
description,
|
||||
duration: 6000
|
||||
})
|
||||
media.mediaListEntry = undefined
|
||||
} else {
|
||||
const error = `\n${429} - ${codes[429]}`
|
||||
console.error('Failed to delete title from user list with: ' + description + error)
|
||||
toast.error('Failed to Delete Title', {
|
||||
description: description + error,
|
||||
duration: 9000
|
||||
})
|
||||
printToast(res, description, false, false)
|
||||
|
||||
if (Helper.getUser().sync) { // handle profile syncing
|
||||
const mediaId = media.id
|
||||
const profiles = JSON.parse(localStorage.getItem('profiles')) || []
|
||||
for (const profile of profiles) {
|
||||
if (profile.viewer?.data?.Viewer.sync) {
|
||||
const anilist = profile.viewer?.data?.Viewer?.avatar
|
||||
const listId = (anilist ? {id: (await anilistClient.getUserLists({userID: profile.viewer.data.Viewer.id, token: profile.token}))?.data?.MediaListCollection?.lists?.flatMap(list => list.entries).find(({ media }) => media.id === mediaId)?.media?.mediaListEntry?.id} : {idMal: media.idMal})
|
||||
if (listId?.id || listId?.idMal) {
|
||||
const res = await Helper.delete({...listId, token: profile.token, anilist})
|
||||
printToast(res, description, false, profile)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (res) media.mediaListEntry = undefined
|
||||
}
|
||||
}
|
||||
|
||||
async function saveChanges() {
|
||||
async function saveEntry() {
|
||||
if (!status.includes('NOT IN LIST')) {
|
||||
const fuzzyDate = Helper.getFuzzyDate(media, status)
|
||||
const lists = media.mediaListEntry?.customLists.filter(list => list.enabled).map(list => list.name) || []
|
||||
if (!lists.includes('Watched using Miru')) {
|
||||
lists.push('Watched using Miru')
|
||||
}
|
||||
const variables = {
|
||||
id: media.id,
|
||||
idMal: media.idMal,
|
||||
|
|
@ -85,30 +92,59 @@
|
|||
episode,
|
||||
score: Helper.isAniAuth() ? (score * 10) : score, // AniList score scale is out of 100, others use a scale of 10.
|
||||
repeat: media.mediaListEntry?.repeat || 0,
|
||||
lists: media.mediaListEntry?.customLists?.filter(list => list.enabled).map(list => list.name) || [],
|
||||
lists,
|
||||
...fuzzyDate
|
||||
}
|
||||
let res = await Helper.entry(media, variables)
|
||||
const description = `Title: ${anilistClient.title(media)}\nStatus: ${Helper.statusName[media.mediaListEntry.status]}\nEpisode: ${episode} / ${totalEpisodes}${score !== 0 ? `\nYour Score: ${score}` : ''}`
|
||||
if (res?.data?.SaveMediaListEntry) {
|
||||
console.log('List Updated: ' + description)
|
||||
toast.success('List Updated', {
|
||||
description,
|
||||
duration: 6000
|
||||
})
|
||||
} else {
|
||||
const error = `\n${429} - ${codes[429]}`
|
||||
console.error('Failed to update user list with: ' + description + error)
|
||||
toast.error('Failed to Update List', {
|
||||
description: description + error,
|
||||
duration: 9000
|
||||
})
|
||||
const description = `Title: ${anilistClient.title(media)}\nStatus: ${Helper.statusName[status]}\nEpisode: ${episode} / ${totalEpisodes}${score !== 0 ? `\nYour Score: ${score}` : ''}`
|
||||
printToast(res, description, true, false)
|
||||
if (Helper.getUser().sync) { // handle profile syncing
|
||||
const mediaId = media.id
|
||||
const profiles = JSON.parse(localStorage.getItem('profiles')) || []
|
||||
for (const profile of profiles) {
|
||||
if (profile.viewer?.data?.Viewer.sync) {
|
||||
const anilist = profile.viewer?.data?.Viewer?.avatar
|
||||
const currentLists = (anilist ? (await anilistClient.getUserLists({userID: profile.viewer.data.Viewer.id, token: profile.token}))?.data?.MediaListCollection?.lists?.flatMap(list => list.entries).find(({ media }) => media.id === mediaId)?.media?.mediaListEntry?.customLists?.filter(list => list.enabled).map(list => list.name) || [] : lists)
|
||||
if (!currentLists.includes('Watched using Miru')) {
|
||||
currentLists.push('Watched using Miru')
|
||||
}
|
||||
const res = await Helper.entry(media, { ...variables, lists: currentLists, score: (anilist ? (score * 10) : score), token: profile.token, anilist })
|
||||
printToast(res, description, true, profile)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
await deleteEntry()
|
||||
}
|
||||
}
|
||||
|
||||
function printToast(res, description, save, profile) {
|
||||
const who = (profile ? ' for ' + profile.viewer.data.Viewer.name + (profile.viewer?.data?.Viewer?.avatar ? ' (AniList)' : ' (MyAnimeList)') : '')
|
||||
if ((save && res?.data?.SaveMediaListEntry) || (!save && res)) {
|
||||
console.log('List Updated' + who + ':\n' + description)
|
||||
if (!profile) {
|
||||
if (save) {
|
||||
toast.success('List Updated', {
|
||||
description,
|
||||
duration: 6000
|
||||
})
|
||||
} else {
|
||||
toast.warning('List Updated', {
|
||||
description,
|
||||
duration: 9000
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const error = `\n${429} - ${codes[429]}`
|
||||
console.error('Failed to ' + (save ? 'update' : 'delete title from') + ' user list' + who + ' with:\n' + description + error)
|
||||
toast.error('Failed to ' + (save ? 'Update' : 'Delete') + ' List' + who, {
|
||||
description: description + error,
|
||||
duration: 9000
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Event & { currentTarget: HTMLInputElement }} event
|
||||
*/
|
||||
|
|
|
|||
Loading…
Reference in a new issue