@@ -183,6 +189,9 @@
.mt-2 {
margin-top: .4rem;
}
+ .z-55 {
+ z-index: 55;
+ }
.mw-400 {
min-width: 35rem;
}
diff --git a/common/components/Search.svelte b/common/components/Search.svelte
index 68e5013..530eaa2 100644
--- a/common/components/Search.svelte
+++ b/common/components/Search.svelte
@@ -1,7 +1,7 @@
diff --git a/common/modules/al.d.ts b/common/modules/al.d.ts
index 2e37fba..cc651ce 100644
--- a/common/modules/al.d.ts
+++ b/common/modules/al.d.ts
@@ -149,6 +149,7 @@ export type Viewer = {
}
name: string
id: number
+ sync: boolean
mediaListOptions?: {
animeList?: {
customLists?: string[]
diff --git a/common/modules/anilist.js b/common/modules/anilist.js
index c5b0d70..13bb378 100644
--- a/common/modules/anilist.js
+++ b/common/modules/anilist.js
@@ -251,6 +251,7 @@ class AnilistClient {
variables: {
page: 1,
perPage: 50,
+ sort: 'TRENDING_DESC',
status_in: '[CURRENT,PLANNING,COMPLETED,DROPPED,PAUSED,REPEATING]',
...variables
}
@@ -569,7 +570,6 @@ class AnilistClient {
async search (variables = {}) {
debug(`Searching ${JSON.stringify(variables)}`)
- variables.sort ||= 'SEARCH_MATCH'
const query = /* js */`
query($page: Int, $perPage: Int, $sort: [MediaSort], $search: String, $onList: Boolean, $status: MediaStatus, $status_not: MediaStatus, $season: MediaSeason, $year: Int, $genre: [String], $tag: [String], $format: MediaFormat, $id_not: [Int], $idMal_not: [Int], $idMal: [Int]) {
Page(page: $page, perPage: $perPage) {
diff --git a/common/modules/helper.js b/common/modules/helper.js
index b9cd4a5..4387c5d 100644
--- a/common/modules/helper.js
+++ b/common/modules/helper.js
@@ -256,30 +256,36 @@ export default class Helper {
}
Object.assign(variables, this.getFuzzyDate(media, status))
+ if (media.mediaListEntry?.status !== variables.status || media.mediaListEntry?.progress !== variables.episode || media.mediaListEntry?.score !== variables.score || media.mediaListEntry?.repeat !== variables.repeat) {
+ 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)
- 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)
-
- if (this.getUser().sync) { // handle profile entry syncing
- const mediaId = media.id
- for (const profile of get(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})
+ if (this.getUser().sync) { // handle profile entry syncing
+ const mediaId = media.id
+ for (const profile of get(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)
}
- this.listToast(res, description, profile)
}
}
+ } else {
+ debug(`No entry changes detected for ${media.title.userPreferred}`)
}
}
}
diff --git a/common/modules/mal.d.ts b/common/modules/mal.d.ts
index b62bcb5..0641735 100644
--- a/common/modules/mal.d.ts
+++ b/common/modules/mal.d.ts
@@ -40,6 +40,7 @@ export type Viewer = {
id: number
name: string
picture: string
+ sync: boolean
}
export type MediaList = {
diff --git a/common/modules/sections.js b/common/modules/sections.js
index 9a99eda..691af85 100644
--- a/common/modules/sections.js
+++ b/common/modules/sections.js
@@ -83,7 +83,7 @@ function createSections () {
}),
// user specific sections
{
- title: 'Sequels You Missed', variables : { sort: 'POPULARITY_DESC', userList: true, disableHide: true },
+ title: 'Sequels You Missed', variables : { sort: 'POPULARITY_DESC', userList: true, missedList: true, disableHide: true },
load: (page = 1, perPage = 50, variables = {}) => {
if (Helper.isMalAuth()) return {} // not going to bother handling this, see below.
const res = Helper.userLists(variables).then(res => {
@@ -100,6 +100,24 @@ function createSections () {
},
hide: !Helper.isAuthorized() || Helper.isMalAuth() // disable this section when authenticated with MyAnimeList. API for userLists fail to return relations and likely will never be fixed on their end.
},
+ {
+ title: 'Stories You Missed', variables : { sort: 'POPULARITY_DESC', userList: true, missedList: true, disableHide: true },
+ load: (page = 1, perPage = 50, variables = {}) => {
+ if (Helper.isMalAuth()) return {} // same as Sequels You Missed
+ const res = Helper.userLists(variables).then(res => {
+ const mediaList = res.data.MediaListCollection.lists.find(({ status }) => status === 'COMPLETED')?.entries
+ const excludeIds = res.data.MediaListCollection.lists.reduce((filtered, { status, entries }) => { return (['CURRENT', 'REPEATING', 'COMPLETED', 'DROPPED', 'PAUSED'].includes(status)) ? filtered.concat(entries) : filtered}, []).map(({ media }) => media.id) || []
+ if (!mediaList) return {}
+ const ids = mediaList.flatMap(({ media }) => {
+ return media.relations.edges.filter(edge => !['SEQUEL', 'CHARACTER', 'OTHER'].includes(edge.relationType))
+ }).map(({ node }) => node.id)
+ if (!ids.length) return {}
+ return anilistClient.searchIDS({ page, perPage, id: ids, id_not: excludeIds, ...SectionsManager.sanitiseObject(variables), status: ['FINISHED', 'RELEASING'] })
+ })
+ return SectionsManager.wrapResponse(res, perPage)
+ },
+ hide: !Helper.isAuthorized() || Helper.isMalAuth()
+ },
{
title: 'Continue Watching', variables: { sort: 'UPDATED_TIME_DESC', userList: true, continueWatching: true, disableHide: true },
load: (page = 1, perPage = 50, variables = {}) => {
@@ -143,7 +161,7 @@ function createSections () {
hide: !Helper.isAuthorized()
},
{
- title: 'Planning List', variables : { test: 'Planning', sort: 'POPULARITY_DESC', userList: true, disableHide: true },
+ title: 'Planning List', variables : { test: 'Planning', sort: 'POPULARITY_DESC', planningList: true, userList: true, disableHide: true },
load: (page = 1, perPage = 50, variables = {}) => {
const res = Helper.userLists(variables).then(res => {
const mediaList = Helper.isAniAuth()
@@ -171,7 +189,7 @@ function createSections () {
hide: !Helper.isAuthorized()
},
{
- title: 'Dropped List', variables : { sort: 'UPDATED_TIME_DESC', userList: true, disableHide: true },
+ title: 'Dropped List', variables : { sort: 'UPDATED_TIME_DESC', droppedList: true, userList: true, disableHide: true },
load: (page = 1, perPage = 50, variables = {}) => {
const res = Helper.userLists(variables).then(res => {
const mediaList = Helper.isAniAuth()
@@ -185,12 +203,13 @@ function createSections () {
hide: !Helper.isAuthorized()
},
// common, non-user specific sections
- { title: 'Popular This Season', variables: { sort: 'POPULARITY_DESC', season: currentSeason, year: currentYear, hideMyAnime: settings.value.hideMyAnime, hideStatus: ['COMPLETED', 'DROPPED'] } },
- { title: 'Trending Now', variables: { sort: 'TRENDING_DESC', hideMyAnime: settings.value.hideMyAnime, hideStatus: ['COMPLETED', 'DROPPED'] } },
- { title: 'All Time Popular', variables: { sort: 'POPULARITY_DESC', hideMyAnime: settings.value.hideMyAnime, hideStatus: ['COMPLETED', 'DROPPED'] } },
- { title: 'Romance', variables: { sort: 'TRENDING_DESC', genre: ['Romance'], hideMyAnime: settings.value.hideMyAnime, hideStatus: ['COMPLETED', 'DROPPED'] } },
- { title: 'Action', variables: { sort: 'TRENDING_DESC', genre: ['Action'], hideMyAnime: settings.value.hideMyAnime, hideStatus: ['COMPLETED', 'DROPPED'] } },
- { title: 'Adventure', variables: { sort: 'TRENDING_DESC', genre: ['Adventure'], hideMyAnime: settings.value.hideMyAnime, hideStatus: ['COMPLETED', 'DROPPED'] } },
- { title: 'Fantasy', variables: { sort: 'TRENDING_DESC', genre: ['Fantasy'], hideMyAnime: settings.value.hideMyAnime, hideStatus: ['COMPLETED', 'DROPPED'] } }
+ { title: 'Popular This Season', variables: { sort: 'POPULARITY_DESC', season: currentSeason, year: currentYear, hideMyAnime: settings.value.hideMyAnime, hideStatus: ['CURRENT', 'REPEATING', 'COMPLETED', 'DROPPED'] } },
+ { title: 'Trending Now', variables: { sort: 'TRENDING_DESC', hideMyAnime: settings.value.hideMyAnime, hideStatus: ['CURRENT', 'REPEATING', 'COMPLETED', 'DROPPED'] } },
+ { title: 'All Time Popular', variables: { sort: 'POPULARITY_DESC', hideMyAnime: settings.value.hideMyAnime, hideStatus: ['CURRENT', 'REPEATING', 'COMPLETED', 'DROPPED'] } },
+ { title: 'Romance', variables: { sort: 'TRENDING_DESC', genre: ['Romance'], hideMyAnime: settings.value.hideMyAnime, hideStatus: ['CURRENT', 'REPEATING', 'COMPLETED', 'DROPPED'] } },
+ { title: 'Action', variables: { sort: 'TRENDING_DESC', genre: ['Action'], hideMyAnime: settings.value.hideMyAnime, hideStatus: ['CURRENT', 'REPEATING', 'COMPLETED', 'DROPPED'] } },
+ { title: 'Adventure', variables: { sort: 'TRENDING_DESC', genre: ['Adventure'], hideMyAnime: settings.value.hideMyAnime, hideStatus: ['CURRENT', 'REPEATING', 'COMPLETED', 'DROPPED'] } },
+ { title: 'Fantasy', variables: { sort: 'TRENDING_DESC', genre: ['Fantasy'], hideMyAnime: settings.value.hideMyAnime, hideStatus: ['CURRENT', 'REPEATING', 'COMPLETED', 'DROPPED'] } },
+ { title: 'Comedy', variables: { sort: 'TRENDING_DESC', genre: ['Comedy'], hideMyAnime: settings.value.hideMyAnime, hideStatus: ['CURRENT', 'REPEATING', 'COMPLETED', 'DROPPED'] } }
]
}
\ No newline at end of file
diff --git a/common/modules/settings.js b/common/modules/settings.js
index a2df937..5321b4f 100644
--- a/common/modules/settings.js
+++ b/common/modules/settings.js
@@ -134,8 +134,9 @@ async function handleMalToken (code, state) {
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
+ debug(`Attempting to refresh authorization token ${token} with the refresh token ${refresh}`)
let response
- if (!refresh || !(refresh.length > 0)) {
+ if (refresh && refresh.length > 0) {
response = await fetch('https://myanimelist.net/v1/oauth2/token', {
method: 'POST',
headers: {
@@ -148,9 +149,9 @@ export async function refreshMalToken (token) {
})
})
}
- if (!refresh || !(refresh.length > 0) || !response?.ok) {
+ 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 || response) })
- debug(`Failed to refresh MyAnimeList User Token ${ !refresh || !(refresh.length > 0) ? 'as the refresh token could not be fetched!' : ': ' + JSON.stringify(response)}`)
+ 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()
diff --git a/common/modules/torrent.js b/common/modules/torrent.js
index 539fa84..ec80e99 100644
--- a/common/modules/torrent.js
+++ b/common/modules/torrent.js
@@ -29,6 +29,7 @@ class TorrentWorker extends EventTarget {
if (torrentRx.exec(text)) {
media.set(null)
add(text)
+ this.dispatch('info', 'A Magnet Link has been detected and is being processed. Files will be loaded shortly...')
}
}
})
diff --git a/common/views/Home/Home.svelte b/common/views/Home/Home.svelte
index 9680256..72cc954 100644
--- a/common/views/Home/Home.svelte
+++ b/common/views/Home/Home.svelte
@@ -17,7 +17,7 @@
for (const sectionTitle of settings.value.homeSections) manager.add(mappedSections[sectionTitle])
if (Helper.getUser()) {
- const userSections = ['Continue Watching', 'Sequels You Missed', 'Planning List', 'Completed List', 'Paused List', 'Dropped List', 'Watching List']
+ const userSections = ['Continue Watching', 'Sequels You Missed', 'Stories You Missed', 'Planning List', 'Completed List', 'Paused List', 'Dropped List', 'Watching List']
Helper.getClient().userLists.subscribe(value => {
if (!value) return
for (const section of manager.sections) {
diff --git a/common/views/Player/MediaHandler.svelte b/common/views/Player/MediaHandler.svelte
index 6199ddb..1d3473c 100644
--- a/common/views/Player/MediaHandler.svelte
+++ b/common/views/Player/MediaHandler.svelte
@@ -3,8 +3,6 @@
import AnimeResolver from '@/modules/animeresolver.js'
import { videoRx } from '@/modules/util.js'
import { tick } from 'svelte'
- import { state } from '../WatchTogether/WatchTogether.svelte'
- import IPC from '@/modules/ipc.js'
import { anilistClient } from "@/modules/anilist.js"
import Debug from 'debug'
@@ -69,7 +67,6 @@
episodeTitle: streamingEpisode && episodeRx.exec(streamingEpisode.title)[2],
thumbnail: streamingEpisode?.thumbnail || media?.coverImage.extraLarge
}
- setDiscordRPC(np)
setMediaSession(np)
nowPlaying.set(np)
}
@@ -210,53 +207,6 @@
: new MediaMetadata({ title: name })
navigator.mediaSession.metadata = metadata
}
-
- function setDiscordRPC (np = nowPlaying.value) {
- const w2g = state.value?.code
- const details = [np.title, np.episodeTitle].filter(i => i).join(' - ') || undefined
- const activity = {
- details,
- state: details && 'Watching Episode ' + ((!np.media?.episodes && np.episode) || ''),
- timestamps: {
- start: Date.now()
- },
- party: {
- size: (np.episode && np.media?.episodes && [np.episode, np.media.episodes]) || undefined
- },
- assets: {
- large_text: np.title,
- large_image: np.thumbnail,
- small_image: 'https://raw.githubusercontent.com/NoCrypt/migu/main/common/public/logo_filled.png',
- small_text: 'https://github.com/NoCrypt/migu'
- },
- instance: true,
- type: 3
- }
- // cannot have buttons and secrets at once
- if (w2g) {
- activity.secrets = {
- join: w2g,
- match: w2g + 'm'
- }
- activity.party.id = w2g + 'p'
- } else {
- activity.buttons = [
- {
- label: 'Download app',
- url: 'https://github.com/NoCrypt/migu/releases/latest'
- },
- {
- label: 'Watch on Migu',
- url: `https://miguapp.pages.dev/anime/${np.media?.id}`
- }
- ]
- }
- IPC.emit('discord', { activity })
- }
- state.subscribe(() => {
- setDiscordRPC()
- return noop
- })
{#if SUPPORTS.isAndroid}
diff --git a/common/views/Settings/InterfaceSettings.svelte b/common/views/Settings/InterfaceSettings.svelte
index 5b890ce..6897bb2 100644
--- a/common/views/Settings/InterfaceSettings.svelte
+++ b/common/views/Settings/InterfaceSettings.svelte
@@ -94,7 +94,7 @@
Home Screen Settings
{#if Helper.isAuthorized()}
-
+
diff --git a/common/views/ViewAnime/Scoring.svelte b/common/views/ViewAnime/Scoring.svelte
index 0477196..73fd598 100644
--- a/common/views/ViewAnime/Scoring.svelte
+++ b/common/views/ViewAnime/Scoring.svelte
@@ -61,7 +61,7 @@
status = 'NOT IN LIST'
if (media.mediaListEntry) {
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`
+ const description = `${anilistClient.title(media)} has been deleted from your list.`
printToast(res, description, false, false)
if (res) media.mediaListEntry = undefined
@@ -99,25 +99,43 @@
lists,
...fuzzyDate
}
- const res = await Helper.entry(media, variables)
+ if (media?.mediaListEntry?.status !== variables.status || media?.mediaListEntry?.progress !== variables.episode || media?.mediaListEntry?.score !== variables.score || media?.mediaListEntry?.repeat !== variables.repeat) {
+ const res = await Helper.entry(media, variables)
- if (res?.data?.SaveMediaListEntry) { media.mediaListEntry = res?.data?.SaveMediaListEntry }
+ if (res?.data?.SaveMediaListEntry) {
+ media.mediaListEntry = res?.data?.SaveMediaListEntry
+ }
- 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
- for (const profile of get(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 Migu')) {
- currentLists.push('Watched using Migu')
+ 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
+ for (const profile of get(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)
}
- const res = await Helper.entry(media, { ...variables, lists: currentLists, score: (anilist ? (score * 10) : score), token: profile.token, anilist })
- printToast(res, description, true, profile)
}
}
+ } else {
+ toast.warning('No Changes to List', {
+ description: `Title: ${anilistClient.title(media)}\nStatus: ${Helper.statusName[status]}\nEpisode: ${episode} / ${totalEpisodes}${score !== 0 ? `\nYour Score: ${score}` : ''}`,
+ duration: 6000
+ })
}
} else {
await deleteEntry()
diff --git a/electron/src/main/discord.js b/electron/src/main/discord.js
index f2be3ec..bdb1c92 100644
--- a/electron/src/main/discord.js
+++ b/electron/src/main/discord.js
@@ -6,7 +6,7 @@ export default class Discord {
defaultStatus = {
activity: {
timestamps: { start: Date.now() },
- details: 'Stream anime torrents, real-time.',
+ details: 'Stream anime torrents',
state: 'Watching anime',
assets: {
small_image: 'logo',
@@ -48,6 +48,10 @@ export default class Discord {
this.toggleRPC(data)
})
+ ipcMain.on('discord-hidden', () => {
+ this.debouncedDiscordRPC(undefined, true)
+ })
+
this.discord.on('ready', async () => {
if (this.rpcEnabled) {
this.setDiscordRPC(this.cachedPresence || this.defaultStatus)