mirror of
https://github.com/NoCrypt/migu.git
synced 2026-04-20 08:02:12 +00:00
feat: typed anilist client
This commit is contained in:
parent
42d4c1c4fa
commit
67c7bf3cc9
21 changed files with 670 additions and 491 deletions
|
|
@ -1,14 +1,14 @@
|
|||
<script context='module'>
|
||||
import { setContext } from 'svelte'
|
||||
import { writable } from 'simple-store-svelte'
|
||||
import { alRequest } from '@/modules/anilist.js'
|
||||
import { anilistClient } from '@/modules/anilist.js'
|
||||
import IPC from '@/modules/ipc.js'
|
||||
|
||||
export const page = writable('home')
|
||||
export const view = writable(null)
|
||||
export async function handleAnime (anime) {
|
||||
view.set(null)
|
||||
view.set((await alRequest({ method: 'SearchIDSingle', id: anime })).data.Media)
|
||||
view.set((await anilistClient.searchIDSingle({ id: anime })).data.Media)
|
||||
}
|
||||
IPC.on('open-anime', handleAnime)
|
||||
IPC.on('schedule', () => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { getContext } from 'svelte'
|
||||
import { alID } from '@/modules/anilist.js'
|
||||
import { anilistClient } from '@/modules/anilist.js'
|
||||
import { media } from '../views/Player/MediaHandler.svelte'
|
||||
import { platformMap } from '@/views/Settings/Settings.svelte'
|
||||
import { toast } from 'svelte-sonner'
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
const links = [
|
||||
{
|
||||
click: () => {
|
||||
if (alID) {
|
||||
if (anilistClient.userID) {
|
||||
$logout = true
|
||||
} else {
|
||||
IPC.emit('open', 'https://anilist.co/api/v2/oauth/authorize?client_id=4254&response_type=token') // Change redirect_url to miru://auth
|
||||
|
|
@ -84,8 +84,8 @@
|
|||
text: 'Settings'
|
||||
}
|
||||
]
|
||||
if (alID) {
|
||||
alID.then(result => {
|
||||
if (anilistClient.userID) {
|
||||
anilistClient.userID.then(result => {
|
||||
if (result?.data?.Viewer) {
|
||||
links[0].image = result.data.Viewer.avatar.medium
|
||||
links[0].text = 'Logout'
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { getContext } from 'svelte'
|
||||
import { alID } from '@/modules/anilist.js'
|
||||
import { anilistClient } from '@/modules/anilist.js'
|
||||
import { media } from '../views/Player/MediaHandler.svelte'
|
||||
import { platformMap } from '@/views/Settings/Settings.svelte'
|
||||
import { settings } from '@/modules/settings.js'
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
const links = [
|
||||
{
|
||||
click: () => {
|
||||
if (alID) {
|
||||
if (anilistClient.userID) {
|
||||
$logout = true
|
||||
} else {
|
||||
IPC.emit('open', 'https://anilist.co/api/v2/oauth/authorize?client_id=4254&response_type=token') // Change redirect_url to miru://auth
|
||||
|
|
@ -85,8 +85,8 @@
|
|||
text: 'Settings'
|
||||
}
|
||||
]
|
||||
if (alID) {
|
||||
alID.then(result => {
|
||||
if (anilistClient.userID) {
|
||||
anilistClient.userID.then(result => {
|
||||
if (result?.data?.Viewer) {
|
||||
links[0].image = result.data.Viewer.avatar.medium
|
||||
links[0].text = 'Logout'
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { formatMap, setStatus, playMedia } from '@/modules/anime.js'
|
||||
import { alRequest } from '@/modules/anilist.js'
|
||||
import { anilistClient } from '@/modules/anilist.js'
|
||||
import { click } from '@/modules/click.js'
|
||||
export let media
|
||||
|
||||
|
|
@ -11,18 +11,12 @@
|
|||
media.mediaListEntry = res.data.SaveMediaListEntry
|
||||
} else {
|
||||
// delete
|
||||
alRequest({
|
||||
method: 'Delete',
|
||||
id: media.mediaListEntry.id
|
||||
})
|
||||
anilistClient.delete({ id: media.mediaListEntry.id })
|
||||
media.mediaListEntry = undefined
|
||||
}
|
||||
}
|
||||
function toggleFavourite () {
|
||||
alRequest({
|
||||
method: 'Favourite',
|
||||
id: media.id
|
||||
})
|
||||
anilistClient.favourite({ id: media.id })
|
||||
media.isFavourite = !media.isFavourite
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { formatMap, setStatus, playMedia } from '@/modules/anime.js'
|
||||
import { alRequest } from '@/modules/anilist.js'
|
||||
import { anilistClient } from '@/modules/anilist.js'
|
||||
import { click } from '@/modules/click.js'
|
||||
export let media
|
||||
|
||||
|
|
@ -27,18 +27,12 @@
|
|||
media.mediaListEntry = res.data.SaveMediaListEntry
|
||||
} else {
|
||||
// delete
|
||||
alRequest({
|
||||
method: 'Delete',
|
||||
id: media.mediaListEntry.id
|
||||
})
|
||||
anilistClient.delete({ id: media.mediaListEntry.id })
|
||||
media.mediaListEntry = undefined
|
||||
}
|
||||
}
|
||||
function toggleFavourite () {
|
||||
alRequest({
|
||||
method: 'Favourite',
|
||||
id: media.id
|
||||
})
|
||||
anilistClient.favourite({ id: media.id })
|
||||
media.isFavourite = !media.isFavourite
|
||||
}
|
||||
function play () {
|
||||
|
|
|
|||
173
common/modules/al.d.ts
vendored
Normal file
173
common/modules/al.d.ts
vendored
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
export type Media = {
|
||||
id: number
|
||||
title: {
|
||||
romaji: string
|
||||
english: string
|
||||
native: string
|
||||
userPreferred: string
|
||||
}
|
||||
description: string
|
||||
season: string
|
||||
seasonYear: string
|
||||
format: string
|
||||
status: string
|
||||
episodes: number
|
||||
duration: number
|
||||
averageScore: number
|
||||
genres: string[]
|
||||
isFavourite: boolean
|
||||
coverImage: {
|
||||
extraLarge: string
|
||||
large: string
|
||||
medium: string
|
||||
}
|
||||
source: string
|
||||
countryOfOrigin: string
|
||||
isAdult: boolean
|
||||
bannerImage: string
|
||||
synonyms: string[]
|
||||
nextAiringEpisode: {
|
||||
episode: number
|
||||
airingAt: number
|
||||
}
|
||||
startDate: {
|
||||
year: number
|
||||
month: number
|
||||
day: number
|
||||
}
|
||||
trailer: {
|
||||
id: string
|
||||
site: string
|
||||
}
|
||||
streamingEpisodes: {
|
||||
title: string
|
||||
thumbnail: string
|
||||
}[]
|
||||
mediaListEntry: {
|
||||
id: number
|
||||
progress: number
|
||||
repeat: number
|
||||
status: string
|
||||
cusomLists: string[]
|
||||
score: number
|
||||
}
|
||||
studios: {
|
||||
edges: {
|
||||
node: {
|
||||
name: string
|
||||
}
|
||||
}[]
|
||||
}
|
||||
relations: {
|
||||
edges: {
|
||||
relationType: string
|
||||
node: {
|
||||
id: number
|
||||
title: {
|
||||
userPreferred: string
|
||||
}
|
||||
type: string
|
||||
status: string
|
||||
format: string
|
||||
episodes: number
|
||||
synonyms: string[]
|
||||
season: string
|
||||
seasonYear: number
|
||||
startDate: {
|
||||
year: number
|
||||
month: number
|
||||
day: number
|
||||
}
|
||||
endDate: {
|
||||
year: number
|
||||
month: number
|
||||
day: number
|
||||
}
|
||||
}
|
||||
}[]
|
||||
}
|
||||
recommendations: {
|
||||
edges: {
|
||||
node: {
|
||||
media: {
|
||||
id: number
|
||||
title: {
|
||||
userPreferred: string
|
||||
}
|
||||
coverImage: {
|
||||
medium: string
|
||||
}
|
||||
}
|
||||
}
|
||||
}[]
|
||||
}
|
||||
}
|
||||
|
||||
export type MediaList = {
|
||||
status: string
|
||||
score: number
|
||||
progress: number
|
||||
user: {
|
||||
name: string
|
||||
avatar: {
|
||||
medium: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type MediaListCollection = {
|
||||
lists: {
|
||||
status: string
|
||||
entries: {
|
||||
media: {
|
||||
id: number
|
||||
status: string
|
||||
mediaListEntry: {
|
||||
progress: number
|
||||
}
|
||||
nextAiringEpisode: {
|
||||
episode: number
|
||||
}
|
||||
relations: {
|
||||
edges: {
|
||||
relationType: string
|
||||
node: {
|
||||
id: number
|
||||
}
|
||||
}[]
|
||||
}
|
||||
}
|
||||
}[]
|
||||
}[]
|
||||
}
|
||||
|
||||
export type Viewer = {
|
||||
avatar: {
|
||||
medium: string
|
||||
}
|
||||
name: string
|
||||
id: number
|
||||
mediaListOptions: {
|
||||
animeList: {
|
||||
customLists: string[]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type Query<T> = {
|
||||
data: T
|
||||
}
|
||||
|
||||
export type PagedQuery<T> = Query<{
|
||||
Page: {
|
||||
pageInfo: {
|
||||
total: number
|
||||
perPage: number
|
||||
currentPage: number
|
||||
lastPage: number
|
||||
hasNextPage: boolean
|
||||
}
|
||||
} & T
|
||||
}>
|
||||
|
||||
// TODO: error responses and nullish values
|
||||
|
|
@ -12,28 +12,13 @@ const codes = {
|
|||
402: 'Payment Required',
|
||||
403: 'Forbidden',
|
||||
404: 'Not Found',
|
||||
405: 'Method Not Allowed',
|
||||
406: 'Not Acceptable',
|
||||
407: 'Proxy Authentication Required',
|
||||
408: 'Request Time-out',
|
||||
409: 'Conflict',
|
||||
410: 'Gone',
|
||||
411: 'Length Required',
|
||||
412: 'Precondition Failed',
|
||||
413: 'Request Entity Too Large',
|
||||
414: 'Request-URI Too Large',
|
||||
415: 'Unsupported Media Type',
|
||||
416: 'Requested Range Not Satisfiable',
|
||||
417: 'Expectation Failed',
|
||||
418: 'I\'m a teapot',
|
||||
422: 'Unprocessable Entity',
|
||||
423: 'Locked',
|
||||
424: 'Failed Dependency',
|
||||
425: 'Unordered Collection',
|
||||
426: 'Upgrade Required',
|
||||
428: 'Precondition Required',
|
||||
429: 'Too Many Requests',
|
||||
431: 'Request Header Fields Too Large',
|
||||
500: 'Internal Server Error',
|
||||
501: 'Not Implemented',
|
||||
502: 'Bad Gateway',
|
||||
|
|
@ -47,70 +32,6 @@ const codes = {
|
|||
511: 'Network Authentication Required'
|
||||
}
|
||||
|
||||
const limiter = new Bottleneck({
|
||||
reservoir: 90,
|
||||
reservoirRefreshAmount: 90,
|
||||
reservoirRefreshInterval: 60 * 1000,
|
||||
maxConcurrent: 10,
|
||||
minTime: 100
|
||||
})
|
||||
|
||||
let rl = null
|
||||
|
||||
limiter.on('failed', async (error, jobInfo) => {
|
||||
printError(error)
|
||||
|
||||
if (error.status === 500) return 1
|
||||
|
||||
if (!error.statusText) {
|
||||
if (!rl) rl = sleep(61 * 1000).then(() => { rl = null })
|
||||
return 61 * 1000
|
||||
}
|
||||
const time = ((error.headers.get('retry-after') || 60) + 1) * 1000
|
||||
if (!rl) rl = sleep(time).then(() => { rl = null })
|
||||
return time
|
||||
})
|
||||
|
||||
const handleRequest = limiter.wrap(async opts => {
|
||||
await rl
|
||||
let res = {}
|
||||
try {
|
||||
res = await fetch('https://graphql.anilist.co', opts)
|
||||
} catch (e) {
|
||||
if (!res || res.status !== 404) throw e
|
||||
}
|
||||
if (!res.ok && (res.status === 429 || res.status === 500)) {
|
||||
throw res
|
||||
}
|
||||
let json = null
|
||||
try {
|
||||
json = await res.json()
|
||||
} catch (error) {
|
||||
if (res.ok) printError(error)
|
||||
}
|
||||
if (!res.ok) {
|
||||
if (json) {
|
||||
for (const error of json?.errors || []) {
|
||||
printError(error)
|
||||
}
|
||||
} else {
|
||||
printError(res)
|
||||
}
|
||||
}
|
||||
return json
|
||||
})
|
||||
|
||||
export let alID = null
|
||||
if (alToken) {
|
||||
alID = alRequest({ method: 'Viewer', token: alToken }).then(result => {
|
||||
const lists = result?.data?.Viewer?.mediaListOptions?.animeList?.customLists || []
|
||||
if (!lists.includes('Watched using Miru')) {
|
||||
alRequest({ method: 'CustomList', lists })
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
function printError (error) {
|
||||
console.warn(error)
|
||||
toast.error('Search Failed', {
|
||||
|
|
@ -119,51 +40,14 @@ function printError (error) {
|
|||
})
|
||||
}
|
||||
|
||||
export async function alEntry (filemedia) {
|
||||
// check if values exist
|
||||
if (filemedia.media && alToken) {
|
||||
const { media } = filemedia
|
||||
// check if media can even be watched, ex: it was resolved incorrectly
|
||||
if (media.status === 'FINISHED' || media.status === 'RELEASING') {
|
||||
// some anime/OVA's can have a single episode, or some movies can have multiple episodes
|
||||
const singleEpisode = (!media.episodes || (media.format === 'MOVIE' && media.episodes === 1)) && 1
|
||||
const videoEpisode = Number(filemedia.episode) || singleEpisode
|
||||
const mediaEpisode = media.episodes || media.nextAiringEpisode?.episode || singleEpisode
|
||||
// check episode range
|
||||
if (videoEpisode && mediaEpisode && (mediaEpisode >= videoEpisode)) {
|
||||
// check user's own watch progress
|
||||
const lists = media.mediaListEntry?.customLists.filter(list => list.enabled).map(list => list.name) || []
|
||||
|
||||
const status = media.mediaListEntry?.status === 'REPEATING' ? 'REPEATING' : 'CURRENT'
|
||||
|
||||
if (!media.mediaListEntry || (media.mediaListEntry?.progress <= videoEpisode) || singleEpisode) {
|
||||
const variables = {
|
||||
method: 'Entry',
|
||||
repeat: media.mediaListEntry?.repeat || 0,
|
||||
id: media.id,
|
||||
status,
|
||||
episode: videoEpisode,
|
||||
lists
|
||||
}
|
||||
if (videoEpisode === mediaEpisode) {
|
||||
variables.status = 'COMPLETED'
|
||||
if (media.mediaListEntry?.status === 'REPEATING') variables.repeat = media.mediaListEntry.repeat + 1
|
||||
}
|
||||
if (!lists.includes('Watched using Miru')) {
|
||||
variables.lists.push('Watched using Miru')
|
||||
}
|
||||
await alRequest(variables)
|
||||
userLists.value = alRequest({ method: 'UserLists' })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const date = new Date()
|
||||
export const currentSeason = ['WINTER', 'SPRING', 'SUMMER', 'FALL'][Math.floor((date.getMonth() / 12) * 4) % 4]
|
||||
export const currentYear = date.getFullYear()
|
||||
|
||||
/**
|
||||
* @param {import('./al.d.ts').Media & {lavenshtein?: number}} media
|
||||
* @param {string} name
|
||||
*/
|
||||
function getDistanceFromTitle (media, name) {
|
||||
if (media) {
|
||||
const titles = Object.values(media.title).filter(v => v).map(title => lavenshtein(title.toLowerCase(), name.toLowerCase()))
|
||||
|
|
@ -175,14 +59,6 @@ function getDistanceFromTitle (media, name) {
|
|||
}
|
||||
}
|
||||
|
||||
export async function alSearch (method) {
|
||||
const res = await alRequest(method)
|
||||
const media = res.data.Page.media.map(media => getDistanceFromTitle(media, method.name))
|
||||
if (!media.length) return res
|
||||
const lowest = media.reduce((prev, curr) => prev.lavenshtein <= curr.lavenshtein ? prev : curr)
|
||||
return { data: { Page: { media: [lowest] } } }
|
||||
}
|
||||
|
||||
const queryObjects = /* js */`
|
||||
id,
|
||||
idMal,
|
||||
|
|
@ -207,6 +83,7 @@ coverImage{
|
|||
medium,
|
||||
color
|
||||
},
|
||||
source,
|
||||
countryOfOrigin,
|
||||
isAdult,
|
||||
bannerImage,
|
||||
|
|
@ -236,7 +113,6 @@ mediaListEntry{
|
|||
customLists(asArray: true),
|
||||
score(format: POINT_10)
|
||||
},
|
||||
source,
|
||||
studios(isMain: true){
|
||||
nodes {
|
||||
name
|
||||
|
|
@ -291,252 +167,410 @@ recommendations{
|
|||
}
|
||||
}`
|
||||
|
||||
export async function alRequest (opts) {
|
||||
let query
|
||||
const variables = {
|
||||
...opts,
|
||||
sort: opts.sort || 'TRENDING_DESC',
|
||||
page: opts.page || 1,
|
||||
perPage: opts.perPage || 30,
|
||||
status_in: opts.status_in || '[CURRENT,PLANNING]'
|
||||
}
|
||||
const options = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json'
|
||||
class AnilistClient {
|
||||
limiter = new Bottleneck({
|
||||
reservoir: 90,
|
||||
reservoirRefreshAmount: 90,
|
||||
reservoirRefreshInterval: 60 * 1000,
|
||||
maxConcurrent: 10,
|
||||
minTime: 100
|
||||
})
|
||||
|
||||
rateLimitPromise = null
|
||||
|
||||
userLists = writable()
|
||||
|
||||
userID
|
||||
|
||||
constructor () {
|
||||
this.limiter.on('failed', async (error, jobInfo) => {
|
||||
printError(error)
|
||||
|
||||
if (error.status === 500) return 1
|
||||
|
||||
if (!error.statusText) {
|
||||
if (!this.rateLimitPromise) this.rateLimitPromise = sleep(61 * 1000).then(() => { this.rateLimitPromise = null })
|
||||
return 61 * 1000
|
||||
}
|
||||
const time = ((error.headers.get('retry-after') || 60) + 1) * 1000
|
||||
if (!this.rateLimitPromise) this.rateLimitPromise = sleep(time).then(() => { this.rateLimitPromise = null })
|
||||
return time
|
||||
})
|
||||
|
||||
if (alToken) {
|
||||
this.userID = this.viewer({ token: alToken }).then(result => {
|
||||
const lists = result?.data?.Viewer?.mediaListOptions?.animeList?.customLists || []
|
||||
if (!lists.includes('Watched using Miru')) {
|
||||
this.customList({ lists })
|
||||
}
|
||||
return result
|
||||
})
|
||||
this.userLists.value = this.getUserLists()
|
||||
// update userLists every 15 mins
|
||||
setInterval(() => { this.userLists.value = this.getUserLists() }, 1000 * 60 * 15)
|
||||
}
|
||||
}
|
||||
if (alToken) options.headers.Authorization = alToken
|
||||
switch (opts.method) {
|
||||
case 'SearchName': {
|
||||
variables.search = opts.name
|
||||
variables.isAdult = variables.isAdult ?? false
|
||||
query = /* js */`
|
||||
query($page: Int, $perPage: Int, $sort: [MediaSort], $search: String, $status: [MediaStatus], $year: Int, $isAdult: Boolean){
|
||||
Page(page: $page, perPage: $perPage){
|
||||
pageInfo{
|
||||
hasNextPage
|
||||
},
|
||||
media(type: ANIME, search: $search, sort: $sort, status_in: $status, isAdult: $isAdult, format_not: MUSIC, seasonYear: $year){
|
||||
${queryObjects}
|
||||
|
||||
/** @type {(options: RequestInit) => Promise<any>} */
|
||||
handleRequest = this.limiter.wrap(async opts => {
|
||||
await this.rateLimitPromise
|
||||
let res = {}
|
||||
try {
|
||||
res = await fetch('https://graphql.anilist.co', opts)
|
||||
} catch (e) {
|
||||
if (!res || res.status !== 404) throw e
|
||||
}
|
||||
}
|
||||
}`
|
||||
break
|
||||
} case 'SearchIDSingle': {
|
||||
query = /* js */`
|
||||
query($id: Int){
|
||||
Media(id: $id, type: ANIME){
|
||||
${queryObjects}
|
||||
}
|
||||
}`
|
||||
break
|
||||
} case 'SearchIDS': {
|
||||
query = /* js */`
|
||||
query($id: [Int], $page: Int, $perPage: Int, $status: [MediaStatus], $onList: Boolean, $sort: [MediaSort], $search: String, $season: MediaSeason, $year: Int, $genre: String, $format: MediaFormat){
|
||||
Page(page: $page, perPage: $perPage){
|
||||
pageInfo{
|
||||
hasNextPage
|
||||
},
|
||||
media(id_in: $id, type: ANIME, status_in: $status, onList: $onList, search: $search, sort: $sort, season: $season, seasonYear: $year, genre: $genre, format: $format){
|
||||
${queryObjects}
|
||||
if (!res.ok && (res.status === 429 || res.status === 500)) {
|
||||
throw res
|
||||
}
|
||||
}
|
||||
}`
|
||||
break
|
||||
} case 'Viewer': {
|
||||
variables.id = alToken
|
||||
query = /* js */`
|
||||
query{
|
||||
Viewer{
|
||||
avatar{
|
||||
medium
|
||||
},
|
||||
name,
|
||||
id,
|
||||
mediaListOptions{
|
||||
animeList{
|
||||
customLists
|
||||
let json = null
|
||||
try {
|
||||
json = await res.json()
|
||||
} catch (error) {
|
||||
if (res.ok) printError(error)
|
||||
}
|
||||
if (!res.ok) {
|
||||
if (json) {
|
||||
for (const error of json?.errors || []) {
|
||||
printError(error)
|
||||
}
|
||||
} else {
|
||||
printError(res)
|
||||
}
|
||||
}
|
||||
return json
|
||||
})
|
||||
|
||||
/**
|
||||
* @param {string} query
|
||||
* @param {object} variables
|
||||
*/
|
||||
alRequest (query, variables) {
|
||||
const options = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
query: query.replace(/\s/g, ''),
|
||||
variables: {
|
||||
sort: 'TRENDING_DESC',
|
||||
page: 1,
|
||||
perPage: 30,
|
||||
status_in: '[CURRENT,PLANNING]',
|
||||
...variables
|
||||
}
|
||||
})
|
||||
}
|
||||
if (alToken) options.headers.Authorization = alToken
|
||||
|
||||
return this.handleRequest(options)
|
||||
}
|
||||
}`
|
||||
break
|
||||
} case 'UserLists': {
|
||||
const userId = (await alID)?.data?.Viewer.id
|
||||
variables.id = userId
|
||||
query = /* js */`
|
||||
query($id: Int){
|
||||
MediaListCollection(userId: $id, type: ANIME, forceSingleCompletedList: true) {
|
||||
lists {
|
||||
status,
|
||||
entries {
|
||||
media {
|
||||
id,
|
||||
status,
|
||||
mediaListEntry {
|
||||
progress
|
||||
},
|
||||
nextAiringEpisode {
|
||||
episode
|
||||
},
|
||||
relations {
|
||||
edges {
|
||||
relationType(version:2)
|
||||
node {
|
||||
id
|
||||
}
|
||||
|
||||
async alSearch (method) {
|
||||
const res = await this.searchName(method)
|
||||
const media = res.data.Page.media.map(media => getDistanceFromTitle(media, method.name))
|
||||
if (!media.length) return null
|
||||
return media.reduce((prev, curr) => prev.lavenshtein <= curr.lavenshtein ? prev : curr)
|
||||
}
|
||||
|
||||
async alEntry (filemedia) {
|
||||
// check if values exist
|
||||
if (filemedia.media && alToken) {
|
||||
const { media } = filemedia
|
||||
// check if media can even be watched, ex: it was resolved incorrectly
|
||||
if (media.status === 'FINISHED' || media.status === 'RELEASING') {
|
||||
// some anime/OVA's can have a single episode, or some movies can have multiple episodes
|
||||
const singleEpisode = (!media.episodes || (media.format === 'MOVIE' && media.episodes === 1)) && 1
|
||||
const videoEpisode = Number(filemedia.episode) || singleEpisode
|
||||
const mediaEpisode = media.episodes || media.nextAiringEpisode?.episode || singleEpisode
|
||||
// check episode range
|
||||
if (videoEpisode && mediaEpisode && (mediaEpisode >= videoEpisode)) {
|
||||
// check user's own watch progress
|
||||
const lists = media.mediaListEntry?.customLists.filter(list => list.enabled).map(list => list.name) || []
|
||||
|
||||
const status = media.mediaListEntry?.status === 'REPEATING' ? 'REPEATING' : 'CURRENT'
|
||||
|
||||
if (!media.mediaListEntry || (media.mediaListEntry?.progress <= videoEpisode) || singleEpisode) {
|
||||
const variables = {
|
||||
repeat: media.mediaListEntry?.repeat || 0,
|
||||
id: media.id,
|
||||
status,
|
||||
episode: videoEpisode,
|
||||
lists
|
||||
}
|
||||
if (videoEpisode === mediaEpisode) {
|
||||
variables.status = 'COMPLETED'
|
||||
if (media.mediaListEntry?.status === 'REPEATING') variables.repeat = media.mediaListEntry.repeat + 1
|
||||
}
|
||||
if (!lists.includes('Watched using Miru')) {
|
||||
variables.lists.push('Watched using Miru')
|
||||
}
|
||||
await this.entry(variables)
|
||||
this.userLists.value = this.getUserLists()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
break
|
||||
} case 'SearchIDStatus': {
|
||||
const userId = (await alID)?.data?.Viewer.id
|
||||
variables.id = userId
|
||||
variables.mediaId = opts.id
|
||||
query = /* js */`
|
||||
query($id: Int, $mediaId: Int){
|
||||
MediaList(userId: $id, mediaId: $mediaId){
|
||||
status,
|
||||
progress,
|
||||
repeat
|
||||
|
||||
/** @returns {Promise<import('./al.d.ts').PagedQuery<{media: import('./al.d.ts').Media[]}>>} */
|
||||
searchName (variables = {}) {
|
||||
const query = /* js */`
|
||||
query($page: Int, $perPage: Int, $sort: [MediaSort], $name: String, $status: [MediaStatus], $year: Int, $isAdult: Boolean){
|
||||
Page(page: $page, perPage: $perPage){
|
||||
pageInfo{
|
||||
hasNextPage
|
||||
},
|
||||
media(type: ANIME, search: $name, sort: $sort, status_in: $status, isAdult: $isAdult, format_not: MUSIC, seasonYear: $year){
|
||||
${queryObjects}
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
variables.isAdult = variables.isAdult ?? false
|
||||
|
||||
return this.alRequest(query, variables)
|
||||
}
|
||||
}`
|
||||
break
|
||||
} case 'AiringSchedule': {
|
||||
variables.to = (variables.from + 7 * 24 * 60 * 60)
|
||||
query = /* js */`
|
||||
query($page: Int, $perPage: Int, $from: Int, $to: Int){
|
||||
Page(page: $page, perPage: $perPage){
|
||||
pageInfo{
|
||||
hasNextPage
|
||||
},
|
||||
airingSchedules(airingAt_greater: $from, airingAt_lesser: $to){
|
||||
episode,
|
||||
timeUntilAiring,
|
||||
airingAt,
|
||||
media{
|
||||
|
||||
/** @returns {Promise<import('./al.d.ts').Query<{Media: import('./al.d.ts').Media}>>} */
|
||||
searchIDSingle (variables) {
|
||||
const query = /* js */`
|
||||
query($id: Int){
|
||||
Media(id: $id, type: ANIME){
|
||||
${queryObjects}
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
return this.alRequest(query, variables)
|
||||
}
|
||||
}`
|
||||
break
|
||||
} case 'Episodes': {
|
||||
query = /* js */`
|
||||
query($id: Int){
|
||||
Page(page: 1, perPage: 1000){
|
||||
airingSchedules(mediaId: $id){
|
||||
airingAt,
|
||||
episode
|
||||
}
|
||||
|
||||
/** @returns {Promise<import('./al.d.ts').PagedQuery<{media: import('./al.d.ts').Media[]}>>} */
|
||||
searchIDS (variables) {
|
||||
const query = /* js */`
|
||||
query($id: [Int], $page: Int, $perPage: Int, $status: [MediaStatus], $onList: Boolean, $sort: [MediaSort], $search: String, $season: MediaSeason, $year: Int, $genre: String, $format: MediaFormat){
|
||||
Page(page: $page, perPage: $perPage){
|
||||
pageInfo{
|
||||
hasNextPage
|
||||
},
|
||||
media(id_in: $id, type: ANIME, status_in: $status, onList: $onList, search: $search, sort: $sort, season: $season, seasonYear: $year, genre: $genre, format: $format){
|
||||
${queryObjects}
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
return this.alRequest(query, variables)
|
||||
}
|
||||
}`
|
||||
break
|
||||
} case 'Search': {
|
||||
variables.sort = opts.sort || 'SEARCH_MATCH'
|
||||
query = /* js */`
|
||||
query($page: Int, $perPage: Int, $sort: [MediaSort], $search: String, $onList: Boolean, $status: MediaStatus, $season: MediaSeason, $year: Int, $genre: String, $format: MediaFormat){
|
||||
Page(page: $page, perPage: $perPage){
|
||||
pageInfo{
|
||||
hasNextPage
|
||||
},
|
||||
media(type: ANIME, search: $search, sort: $sort, onList: $onList, status: $status, season: $season, seasonYear: $year, genre: $genre, format: $format, format_not: MUSIC){
|
||||
${queryObjects}
|
||||
}
|
||||
}
|
||||
}`
|
||||
break
|
||||
} case 'Entry': {
|
||||
query = /* js */`
|
||||
mutation($lists: [String], $id: Int, $status: MediaListStatus, $episode: Int, $repeat: Int, $score: Int){
|
||||
SaveMediaListEntry(mediaId: $id, status: $status, progress: $episode, repeat: $repeat, scoreRaw: $score, customLists: $lists){
|
||||
id,
|
||||
status,
|
||||
progress,
|
||||
repeat
|
||||
}
|
||||
}`
|
||||
break
|
||||
} case 'EpisodeDate': {
|
||||
query = /* js */`
|
||||
query($id: Int, $ep: Int) {
|
||||
AiringSchedule(mediaId: $id, episode: $ep) {
|
||||
airingAt
|
||||
}
|
||||
}`
|
||||
break
|
||||
} case 'Delete': {
|
||||
query = /* js */`
|
||||
mutation($id: Int){
|
||||
DeleteMediaListEntry(id: $id){
|
||||
deleted
|
||||
}
|
||||
}`
|
||||
break
|
||||
} case 'Favourite': {
|
||||
query = /* js */`
|
||||
mutation($id: Int){
|
||||
ToggleFavourite(animeId: $id){ anime { nodes { id } } }
|
||||
}`
|
||||
break
|
||||
} case 'Following': {
|
||||
query = /* js */`
|
||||
query($id: Int){
|
||||
Page{
|
||||
pageInfo{
|
||||
total,
|
||||
perPage,
|
||||
currentPage,
|
||||
lastPage,
|
||||
hasNextPage
|
||||
},
|
||||
mediaList(mediaId: $id, isFollowing: true, sort: UPDATED_TIME_DESC){
|
||||
id,
|
||||
status,
|
||||
score,
|
||||
progress,
|
||||
user{
|
||||
id,
|
||||
name,
|
||||
|
||||
/** @returns {Promise<import('./al.d.ts').Query<{ Viewer: import('./al.d.ts').Viewer }>>} */
|
||||
viewer (variables = {}) {
|
||||
variables.id = alToken
|
||||
const query = /* js */`
|
||||
query{
|
||||
Viewer{
|
||||
avatar{
|
||||
medium
|
||||
},
|
||||
name,
|
||||
id,
|
||||
mediaListOptions{
|
||||
scoreFormat
|
||||
animeList{
|
||||
customLists
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
break
|
||||
} case 'CustomList':{
|
||||
variables.lists = [...opts.lists, 'Watched using Miru']
|
||||
query = /* js */`
|
||||
mutation($lists: [String]){
|
||||
UpdateUser(animeListOptions: { customLists: $lists }){
|
||||
id
|
||||
}
|
||||
}`
|
||||
break
|
||||
}
|
||||
}
|
||||
options.body = JSON.stringify({
|
||||
query: query.replace(/\s/g, ''),
|
||||
variables
|
||||
})
|
||||
}`
|
||||
|
||||
return handleRequest(options)
|
||||
return this.alRequest(query, variables)
|
||||
}
|
||||
|
||||
/** @returns {Promise<import('./al.d.ts').Query<{ MediaListCollection: import('./al.d.ts').MediaListCollection }>>} */
|
||||
async getUserLists (variables = {}) {
|
||||
const userId = (await this.userID)?.data?.Viewer.id
|
||||
variables.id = userId
|
||||
const query = /* js */`
|
||||
query($id: Int){
|
||||
MediaListCollection(userId: $id, type: ANIME, forceSingleCompletedList: true) {
|
||||
lists {
|
||||
status,
|
||||
entries {
|
||||
media {
|
||||
id,
|
||||
status,
|
||||
mediaListEntry {
|
||||
progress
|
||||
},
|
||||
nextAiringEpisode {
|
||||
episode
|
||||
},
|
||||
relations {
|
||||
edges {
|
||||
relationType(version:2)
|
||||
node {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
return await this.alRequest(query, variables)
|
||||
}
|
||||
|
||||
/** @returns {Promise<import('./al.d.ts').Query<{ MediaList: { status: string, progress: number, repeat: number }}>>} */
|
||||
async searchIDStatus (variables = {}) {
|
||||
const userId = (await this.userID)?.data?.Viewer.id
|
||||
variables.id = userId
|
||||
const query = /* js */`
|
||||
query($id: Int, $mediaId: Int){
|
||||
MediaList(userId: $id, mediaId: $mediaId){
|
||||
status,
|
||||
progress,
|
||||
repeat
|
||||
}
|
||||
}`
|
||||
|
||||
return await this.alRequest(query, variables)
|
||||
}
|
||||
|
||||
/** @returns {Promise<import('./al.d.ts').PagedQuery<{ airingSchedule: { timeUntilAiring: number, airingAt: number, episode: number, media: import('./al.d.ts').Media[]}}>>} */
|
||||
searchAiringSchedule (variables = {}) {
|
||||
variables.to = (variables.from + 7 * 24 * 60 * 60)
|
||||
const query = /* js */`
|
||||
query($page: Int, $perPage: Int, $from: Int, $to: Int){
|
||||
Page(page: $page, perPage: $perPage){
|
||||
pageInfo{
|
||||
hasNextPage
|
||||
},
|
||||
airingSchedules(airingAt_greater: $from, airingAt_lesser: $to){
|
||||
episode,
|
||||
timeUntilAiring,
|
||||
airingAt,
|
||||
media{
|
||||
${queryObjects}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
return this.alRequest(query, variables)
|
||||
}
|
||||
|
||||
/** @returns {Promise<import('./al.d.ts').PagedQuery<{ airingSchedules: { airingAt: number, episode: number }[]}>>} */
|
||||
episodes (variables) {
|
||||
const query = /* js */`
|
||||
query($id: Int){
|
||||
Page(page: 1, perPage: 1000){
|
||||
airingSchedules(mediaId: $id){
|
||||
airingAt,
|
||||
episode
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
return this.alRequest(query, variables)
|
||||
}
|
||||
|
||||
/** @returns {Promise<import('./al.d.ts').PagedQuery<{media: import('./al.d.ts').Media[]}>>} */
|
||||
search (variables = {}) {
|
||||
variables.sort ||= 'SEARCH_MATCH'
|
||||
const query = /* js */`
|
||||
query($page: Int, $perPage: Int, $sort: [MediaSort], $search: String, $onList: Boolean, $status: MediaStatus, $season: MediaSeason, $year: Int, $genre: String, $format: MediaFormat){
|
||||
Page(page: $page, perPage: $perPage){
|
||||
pageInfo{
|
||||
hasNextPage
|
||||
},
|
||||
media(type: ANIME, search: $search, sort: $sort, onList: $onList, status: $status, season: $season, seasonYear: $year, genre: $genre, format: $format, format_not: MUSIC){
|
||||
${queryObjects}
|
||||
}
|
||||
}
|
||||
}`
|
||||
return this.alRequest(query, variables)
|
||||
}
|
||||
|
||||
/** @returns {Promise<import('./al.d.ts').Query<{ AiringSchedule: { airingAt: number }}>>} */
|
||||
episodeDate (variables) {
|
||||
const query = /* js */`
|
||||
query($id: Int, $ep: Int) {
|
||||
AiringSchedule(mediaId: $id, episode: $ep) {
|
||||
airingAt
|
||||
}
|
||||
}`
|
||||
|
||||
return this.alRequest(query, variables)
|
||||
}
|
||||
|
||||
/** @returns {Promise<import('./al.d.ts').PagedQuery<{ mediaList: import('./al.d.ts').MediaList[]}>>} */
|
||||
following (variables) {
|
||||
const query = /* js */`
|
||||
query($id: Int){
|
||||
Page{
|
||||
mediaList(mediaId: $id, isFollowing: true, sort: UPDATED_TIME_DESC){
|
||||
status,
|
||||
score,
|
||||
progress,
|
||||
user{
|
||||
name,
|
||||
avatar{
|
||||
medium
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
return this.alRequest(query, variables)
|
||||
}
|
||||
|
||||
entry (variables) {
|
||||
const query = /* js */`
|
||||
mutation($lists: [String], $id: Int, $status: MediaListStatus, $episode: Int, $repeat: Int, $score: Int){
|
||||
SaveMediaListEntry(mediaId: $id, status: $status, progress: $episode, repeat: $repeat, scoreRaw: $score, customLists: $lists){
|
||||
id,
|
||||
status,
|
||||
progress,
|
||||
repeat
|
||||
}
|
||||
}`
|
||||
|
||||
return this.alRequest(query, variables)
|
||||
}
|
||||
|
||||
delete (variables) {
|
||||
const query = /* js */`
|
||||
mutation($id: Int){
|
||||
DeleteMediaListEntry(id: $id){
|
||||
deleted
|
||||
}
|
||||
}`
|
||||
|
||||
return this.alRequest(query, variables)
|
||||
}
|
||||
|
||||
favourite (variables) {
|
||||
const query = /* js */`
|
||||
mutation($id: Int){
|
||||
ToggleFavourite(animeId: $id){ anime { nodes { id } } }
|
||||
}`
|
||||
|
||||
return this.alRequest(query, variables)
|
||||
}
|
||||
|
||||
customList (variables = {}) {
|
||||
variables.lists = [...variables.lists, 'Watched using Miru']
|
||||
const query = /* js */`
|
||||
mutation($lists: [String]){
|
||||
UpdateUser(animeListOptions: { customLists: $lists }){
|
||||
id
|
||||
}
|
||||
}`
|
||||
|
||||
return this.alRequest(query, variables)
|
||||
}
|
||||
}
|
||||
|
||||
export const userLists = writable(alToken && alRequest({ method: 'UserLists' }))
|
||||
|
||||
if (alToken) {
|
||||
// update userLists every 15 mins
|
||||
setInterval(() => { userLists.value = alRequest({ method: 'UserLists' }) }, 1000 * 60 * 15)
|
||||
}
|
||||
export const anilistClient = new AnilistClient()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { DOMPARSER } from './util.js'
|
||||
import { alRequest } from './anilist.js'
|
||||
import { anilistClient } from './anilist.js'
|
||||
import _anitomyscript from 'anitomyscript'
|
||||
import { toast } from 'svelte-sonner'
|
||||
import SectionsManager from './sections.js'
|
||||
|
|
@ -63,7 +63,7 @@ export async function traceAnime (image) { // WAIT lookup logic
|
|||
search.value = {
|
||||
clearNext: true,
|
||||
load: (page = 1, perPage = 50, variables = {}) => {
|
||||
const res = alRequest({ method: 'SearchIDS', page, perPage, id: ids, ...SectionsManager.sanitiseObject(variables) }).then(res => {
|
||||
const res = anilistClient.searchIDS({ page, perPage, id: ids, ...SectionsManager.sanitiseObject(variables) }).then(res => {
|
||||
for (const index in res.data?.Page?.media) {
|
||||
const media = res.data.Page.media[index]
|
||||
const counterpart = result.find(({ anilist }) => anilist === media.id)
|
||||
|
|
@ -226,12 +226,11 @@ export async function playMedia (media) {
|
|||
|
||||
export function setStatus (status, other = {}, media) {
|
||||
const variables = {
|
||||
method: 'Entry',
|
||||
id: media.id,
|
||||
status,
|
||||
...other
|
||||
}
|
||||
return alRequest(variables)
|
||||
return anilistClient.entry(variables)
|
||||
}
|
||||
|
||||
const episodeMetadataMap = {}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { toast } from 'svelte-sonner'
|
||||
import { alRequest, alSearch } from './anilist.js'
|
||||
import { anilistClient } from './anilist.js'
|
||||
import { anitomyscript } from './anime.js'
|
||||
import { PromiseBatch } from './util.js'
|
||||
|
||||
|
|
@ -27,52 +27,52 @@ export default new class AnimeResolver {
|
|||
*/
|
||||
async findAnimeByTitle (parseObject) {
|
||||
const name = parseObject.anime_title
|
||||
const method = { name, method: 'SearchName', perPage: 10, status: ['RELEASING', 'FINISHED'], sort: 'SEARCH_MATCH' }
|
||||
if (parseObject.anime_year) method.year = parseObject.anime_year
|
||||
const variables = { name, perPage: 10, status: ['RELEASING', 'FINISHED'], sort: 'SEARCH_MATCH' }
|
||||
if (parseObject.anime_year) variables.year = parseObject.anime_year
|
||||
|
||||
// inefficient but readable
|
||||
|
||||
let media = null
|
||||
try {
|
||||
// change S2 into Season 2 or 2nd Season
|
||||
const match = method.name.match(/ S(\d+)/)
|
||||
const oldname = method.name
|
||||
const match = variables.name.match(/ S(\d+)/)
|
||||
const oldname = variables.name
|
||||
if (match) {
|
||||
if (Number(match[1]) === 1) { // if this is S1, remove the " S1" or " S01"
|
||||
method.name = method.name.replace(/ S(\d+)/, '')
|
||||
media = (await alSearch(method)).data.Page.media[0]
|
||||
variables.name = variables.name.replace(/ S(\d+)/, '')
|
||||
media = await anilistClient.alSearch(variables)
|
||||
} else {
|
||||
method.name = method.name.replace(/ S(\d+)/, ` ${Number(match[1])}${postfix[Number(match[1])] || 'th'} Season`)
|
||||
media = (await alSearch(method)).data.Page.media[0]
|
||||
variables.name = variables.name.replace(/ S(\d+)/, ` ${Number(match[1])}${postfix[Number(match[1])] || 'th'} Season`)
|
||||
media = await anilistClient.alSearch(variables)
|
||||
if (!media) {
|
||||
method.name = oldname.replace(/ S(\d+)/, ` Season ${Number(match[1])}`)
|
||||
media = (await alSearch(method)).data.Page.media[0]
|
||||
variables.name = oldname.replace(/ S(\d+)/, ` Season ${Number(match[1])}`)
|
||||
media = await anilistClient.alSearch(variables)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
media = (await alSearch(method)).data.Page.media[0]
|
||||
media = await anilistClient.alSearch(variables)
|
||||
}
|
||||
|
||||
// remove - :
|
||||
if (!media) {
|
||||
const match = method.name.match(/[-:]/g)
|
||||
const match = variables.name.match(/[-:]/g)
|
||||
if (match) {
|
||||
method.name = method.name.replace(/[-:]/g, '')
|
||||
media = (await alSearch(method)).data.Page.media[0]
|
||||
variables.name = variables.name.replace(/[-:]/g, '')
|
||||
media = await anilistClient.alSearch(variables)
|
||||
}
|
||||
}
|
||||
// remove (TV)
|
||||
if (!media) {
|
||||
const match = method.name.match(/\(TV\)/)
|
||||
const match = variables.name.match(/\(TV\)/)
|
||||
if (match) {
|
||||
method.name = method.name.replace('(TV)', '')
|
||||
media = (await alSearch(method)).data.Page.media[0]
|
||||
variables.name = variables.name.replace('(TV)', '')
|
||||
media = await anilistClient.alSearch(variables)
|
||||
}
|
||||
}
|
||||
// check adult
|
||||
if (!media) {
|
||||
method.isAdult = true
|
||||
media = (await alSearch(method)).data.Page.media[0]
|
||||
variables.isAdult = true
|
||||
media = await anilistClient.alSearch(variables)
|
||||
}
|
||||
} catch (e) { }
|
||||
|
||||
|
|
@ -88,7 +88,7 @@ export default new class AnimeResolver {
|
|||
* @returns {any}
|
||||
*/
|
||||
getAnimeById (id) {
|
||||
if (!this.animeCache[id]) this.animeCache[id] = alRequest({ method: 'SearchIDSingle', id })
|
||||
if (!this.animeCache[id]) this.animeCache[id] = anilistClient.searchIDSingle({ id })
|
||||
|
||||
return this.animeCache[id]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { alRequest } from '@/modules/anilist.js'
|
||||
import { anilistClient } from '@/modules/anilist.js'
|
||||
import { settings } from '@/modules/settings.js'
|
||||
import { findEdge, resolveSeason, getMediaMaxEp, mapBestRelease } from '../anime.js'
|
||||
import { exclusions, getRSSContent, parseRSSNodes } from '../rss.js'
|
||||
|
|
@ -58,7 +58,7 @@ export default async function getRSSEntries ({ media, episode, mode, ignoreQuali
|
|||
// recursive, get all entries for media sequel, and its sequel, and its sequel
|
||||
const sequelEntries =
|
||||
(sequel?.status === 'FINISHED' || sequel?.status === 'RELEASING') &&
|
||||
(await getRSSEntries({ media: (await alRequest({ method: 'SearchIDSingle', id: sequel.id })).data.Media, episode, mode: mode || 'check' }))
|
||||
(await getRSSEntries({ media: (await anilistClient.searchIDSingle({ id: sequel.id })).data.Media, episode, mode: mode || 'check' }))
|
||||
|
||||
const checkPrequelDate = (media.status === 'FINISHED' || media.status === 'RELEASING') && prequel?.status === 'FINISHED' && prequel?.endDate
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { anitomyscript } from '../anime.js'
|
|||
import { fastPrettyBytes, sleep } from '../util.js'
|
||||
import { exclusions } from '../rss.js'
|
||||
import { settings } from '@/modules/settings.js'
|
||||
import { alRequest } from '../anilist.js'
|
||||
import { anilistClient } from '../anilist.js'
|
||||
import { client } from '@/modules/torrent.js'
|
||||
import mapBestSneedexReleases from './sneedex.js'
|
||||
import getSeedexBests from './seadex.js'
|
||||
|
|
@ -92,7 +92,7 @@ async function getAniDBEpisodeFromAL ({ media, episode }, { episodes, episodeCou
|
|||
// if media has no specials or their episode counts don't match
|
||||
if (!specialCount || (media.episodes && media.episodes === episodeCount && episodes[Number(episode)])) return episodes[Number(episode)]
|
||||
console.log('EP count doesn\'t match, checking by air date')
|
||||
const res = await alRequest({ method: 'EpisodeDate', id: media.id, ep: episode })
|
||||
const res = await anilistClient.episodeDate({ id: media.id, ep: episode })
|
||||
// TODO: if media only has one episode, and airdate doesn't exist use start/release/end dates
|
||||
const alDate = new Date((res.data.AiringSchedule?.airingAt || 0) * 1000)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { alRequest, currentSeason, currentYear, userLists } from '@/modules/anilist.js'
|
||||
import { anilistClient, currentSeason, currentYear } from '@/modules/anilist.js'
|
||||
import { writable } from 'simple-store-svelte'
|
||||
import { settings, alToken } from '@/modules/settings.js'
|
||||
import { RSSManager } from '@/modules/rss.js'
|
||||
|
|
@ -24,8 +24,8 @@ export default class SectionsManager {
|
|||
|
||||
static createFallbackLoad (variables, type) {
|
||||
return (page = 1, perPage = 50, search = variables) => {
|
||||
const options = { method: 'Search', page, perPage, ...SectionsManager.sanitiseObject(search) }
|
||||
const res = alRequest(options)
|
||||
const options = { page, perPage, ...SectionsManager.sanitiseObject(search) }
|
||||
const res = anilistClient.search(options)
|
||||
return SectionsManager.wrapResponse(res, perPage, type)
|
||||
}
|
||||
}
|
||||
|
|
@ -83,7 +83,7 @@ function createSections () {
|
|||
{
|
||||
title: 'Continue Watching',
|
||||
load: (page = 1, perPage = 50, variables = {}) => {
|
||||
const res = userLists.value.then(res => {
|
||||
const res = anilistClient.userLists.value.then(res => {
|
||||
const mediaList = res.data.MediaListCollection.lists.reduce((filtered, { status, entries }) => {
|
||||
return (status === 'CURRENT' || status === 'REPEATING') ? filtered.concat(entries) : filtered
|
||||
}, [])
|
||||
|
|
@ -91,7 +91,7 @@ function createSections () {
|
|||
if (media.status === 'FINISHED') return true
|
||||
return media.mediaListEntry?.progress < media.nextAiringEpisode?.episode - 1
|
||||
}).map(({ media }) => media.id)
|
||||
return alRequest({ method: 'SearchIDS', page, perPage, id: ids, ...SectionsManager.sanitiseObject(variables) })
|
||||
return anilistClient.searchIDS({ page, perPage, id: ids, ...SectionsManager.sanitiseObject(variables) })
|
||||
})
|
||||
return SectionsManager.wrapResponse(res, perPage)
|
||||
},
|
||||
|
|
@ -100,13 +100,13 @@ function createSections () {
|
|||
{
|
||||
title: 'Sequels You Missed',
|
||||
load: (page = 1, perPage = 50, variables = {}) => {
|
||||
const res = userLists.value.then(res => {
|
||||
const res = anilistClient.userLists.value.then(res => {
|
||||
const mediaList = res.data.MediaListCollection.lists.find(({ status }) => status === 'COMPLETED')?.entries
|
||||
if (!mediaList) return {}
|
||||
const ids = mediaList.flatMap(({ media }) => {
|
||||
return media.relations.edges.filter(edge => edge.relationType === 'SEQUEL')
|
||||
}).map(({ node }) => node.id)
|
||||
return alRequest({ method: 'SearchIDS', page, perPage, id: ids, ...SectionsManager.sanitiseObject(variables), status: ['FINISHED', 'RELEASING'], onList: false })
|
||||
return anilistClient.searchIDS({ page, perPage, id: ids, ...SectionsManager.sanitiseObject(variables), status: ['FINISHED', 'RELEASING'], onList: false })
|
||||
})
|
||||
return SectionsManager.wrapResponse(res, perPage)
|
||||
},
|
||||
|
|
@ -115,10 +115,10 @@ function createSections () {
|
|||
{
|
||||
title: 'Your List',
|
||||
load: (page = 1, perPage = 50, variables = {}) => {
|
||||
const res = userLists.value.then(res => {
|
||||
const res = anilistClient.userLists.value.then(res => {
|
||||
const ids = res.data.MediaListCollection.lists.find(({ status }) => status === 'PLANNING')?.entries.map(({ media }) => media.id)
|
||||
if (!ids) return {}
|
||||
return alRequest({ method: 'SearchIDS', page, perPage, id: ids, ...SectionsManager.sanitiseObject(variables) })
|
||||
return anilistClient.searchIDS({ page, perPage, id: ids, ...SectionsManager.sanitiseObject(variables) })
|
||||
})
|
||||
return SectionsManager.wrapResponse(res, perPage)
|
||||
},
|
||||
|
|
@ -127,10 +127,10 @@ function createSections () {
|
|||
{
|
||||
title: 'Completed List',
|
||||
load: (page = 1, perPage = 50, variables = {}) => {
|
||||
const res = userLists.value.then(res => {
|
||||
const res = anilistClient.userLists.value.then(res => {
|
||||
const ids = res.data.MediaListCollection.lists.find(({ status }) => status === 'COMPLETED')?.entries.map(({ media }) => media.id)
|
||||
if (!ids) return {}
|
||||
return alRequest({ method: 'SearchIDS', page, perPage, id: ids, ...SectionsManager.sanitiseObject(variables) })
|
||||
return anilistClient.searchIDS({ page, perPage, id: ids, ...SectionsManager.sanitiseObject(variables) })
|
||||
})
|
||||
return SectionsManager.wrapResponse(res, perPage)
|
||||
},
|
||||
|
|
@ -139,10 +139,10 @@ function createSections () {
|
|||
{
|
||||
title: 'Paused List',
|
||||
load: (page = 1, perPage = 50, variables = {}) => {
|
||||
const res = userLists.value.then(res => {
|
||||
const res = anilistClient.userLists.value.then(res => {
|
||||
const ids = res.data.MediaListCollection.lists.find(({ status }) => status === 'PAUSED')?.entries.map(({ media }) => media.id)
|
||||
if (!ids) return {}
|
||||
return alRequest({ method: 'SearchIDS', page, perPage, id: ids, ...SectionsManager.sanitiseObject(variables) })
|
||||
return anilistClient.searchIDS({ page, perPage, id: ids, ...SectionsManager.sanitiseObject(variables) })
|
||||
})
|
||||
return SectionsManager.wrapResponse(res, perPage)
|
||||
},
|
||||
|
|
@ -151,10 +151,10 @@ function createSections () {
|
|||
{
|
||||
title: 'Dropped List',
|
||||
load: (page = 1, perPage = 50, variables = {}) => {
|
||||
const res = userLists.value.then(res => {
|
||||
const res = anilistClient.userLists.value.then(res => {
|
||||
const ids = res.data.MediaListCollection.lists.find(({ status }) => status === 'DROPPED')?.entries.map(({ media }) => media.id)
|
||||
if (!ids) return {}
|
||||
return alRequest({ method: 'SearchIDS', page, perPage, id: ids, ...SectionsManager.sanitiseObject(variables) })
|
||||
return anilistClient.searchIDS({ page, perPage, id: ids, ...SectionsManager.sanitiseObject(variables) })
|
||||
})
|
||||
return SectionsManager.wrapResponse(res, perPage)
|
||||
},
|
||||
|
|
@ -163,10 +163,10 @@ function createSections () {
|
|||
{
|
||||
title: 'Currently Watching List',
|
||||
load: (page = 1, perPage = 50, variables = {}) => {
|
||||
const res = userLists.value.then(res => {
|
||||
const res = anilistClient.userLists.value.then(res => {
|
||||
const ids = res.data.MediaListCollection.lists.find(({ status }) => status === 'CURRENT')?.entries.map(({ media }) => media.id)
|
||||
if (!ids) return {}
|
||||
return alRequest({ method: 'SearchIDS', page, perPage, id: ids, ...SectionsManager.sanitiseObject(variables) })
|
||||
return anilistClient.searchIDS({ page, perPage, id: ids, ...SectionsManager.sanitiseObject(variables) })
|
||||
})
|
||||
return SectionsManager.wrapResponse(res, perPage)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script context='module'>
|
||||
import SectionsManager from '@/modules/sections.js'
|
||||
import Search, { search } from './Search.svelte'
|
||||
import { alRequest, currentSeason, currentYear } from '@/modules/anilist.js'
|
||||
import { anilistClient, currentSeason, currentYear } from '@/modules/anilist.js'
|
||||
|
||||
const vars = { format: 'TV', season: currentSeason, year: currentYear }
|
||||
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
const variables = { ..._variables }
|
||||
const results = { data: { Page: { media: [], pageInfo: { hasNextPage: false } } } }
|
||||
for (let page = 1, hasNextPage = true; hasNextPage && page < 5; ++page) {
|
||||
const res = await alRequest({ method: 'Search', page, perPage: 50, ...vars, ...SectionsManager.sanitiseObject(variables) })
|
||||
const res = await anilistClient.search({ page, perPage: 50, ...vars, ...SectionsManager.sanitiseObject(variables) })
|
||||
hasNextPage = res.data.Page.pageInfo.hasNextPage
|
||||
results.data.Page.media = results.data.Page.media.concat(res.data.Page.media)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
<script context='module'>
|
||||
import SectionsManager, { sections } from '@/modules/sections.js'
|
||||
import { alToken, settings } from '@/modules/settings.js'
|
||||
import { alRequest, currentSeason, currentYear, userLists } from '@/modules/anilist.js'
|
||||
import { anilistClient, currentSeason, currentYear } from '@/modules/anilist.js'
|
||||
|
||||
const bannerData = alRequest({ method: 'Search', sort: 'POPULARITY_DESC', perPage: 1, onList: false, season: currentSeason, year: currentYear })
|
||||
const bannerData = anilistClient.search({ method: 'Search', sort: 'POPULARITY_DESC', perPage: 1, onList: false, season: currentSeason, year: currentYear })
|
||||
|
||||
const manager = new SectionsManager()
|
||||
|
||||
|
|
@ -18,7 +18,7 @@
|
|||
if (alToken) {
|
||||
const userSections = ['Continue Watching', 'Sequels You Missed', 'Your List', 'Completed List', 'Paused List', 'Dropped List', 'Currently Watching List']
|
||||
|
||||
userLists.subscribe(() => {
|
||||
anilistClient.userLists.subscribe(() => {
|
||||
for (const section of manager.sections) {
|
||||
// remove preview value, to force UI to re-request data, which updates it once in viewport
|
||||
if (userSections.includes(section.title)) section.preview.value = section.load(1, 15)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import { playAnime } from '../RSSView.svelte'
|
||||
import { client } from '@/modules/torrent.js'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { alEntry } from '@/modules/anilist.js'
|
||||
import { anilistClient } from '@/modules/anilist.js'
|
||||
import Subtitles from '@/modules/subtitles.js'
|
||||
import { toTS, fastPrettyBytes, videoRx } from '@/modules/util.js'
|
||||
import { toast } from 'svelte-sonner'
|
||||
|
|
@ -861,7 +861,7 @@
|
|||
if (media?.media?.episodes || media?.media?.nextAiringEpisode?.episode) {
|
||||
if (media.media.episodes || media.media.nextAiringEpisode?.episode > media.episode) {
|
||||
completed = true
|
||||
alEntry(media)
|
||||
anilistClient.alEntry(media)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import { alToken } from '../../views/Settings.svelte'
|
||||
import { addToast } from '../../components/Toasts.svelte'
|
||||
import IPC from '@/modules/ipc.js'
|
||||
import { alRequest } from '@/modules/anilist.js'
|
||||
import { anilistClient } from '@/modules/anilist.js'
|
||||
import { getContext } from 'svelte'
|
||||
import { getMediaMaxEp } from '@/modules/anime.js'
|
||||
import { playAnime } from '../RSSView.svelte'
|
||||
|
|
@ -22,12 +22,7 @@
|
|||
// add
|
||||
await setStatus(toggleStatusMap[media.mediaListEntry?.status] || 'PLANNING')
|
||||
} else {
|
||||
// delete
|
||||
const variables = {
|
||||
method: 'Delete',
|
||||
id: media.mediaListEntry.id
|
||||
}
|
||||
await alRequest(variables)
|
||||
await anilistClient.delete({ id: media.mediaListEntry.id })
|
||||
}
|
||||
update()
|
||||
}
|
||||
|
|
@ -41,24 +36,22 @@
|
|||
}
|
||||
function setStatus (status, other = {}) {
|
||||
const variables = {
|
||||
method: 'Entry',
|
||||
id: media.id,
|
||||
status,
|
||||
...other
|
||||
}
|
||||
return alRequest(variables)
|
||||
return anilistClient.entry(variables)
|
||||
}
|
||||
async function update () {
|
||||
media = (await alRequest({ method: 'SearchIDSingle', id: media.id })).data.Media
|
||||
media = (await anilistClient.searchIDSingle({ id: media.id })).data.Media
|
||||
}
|
||||
async function score (media, score) {
|
||||
const variables = {
|
||||
method: 'Entry',
|
||||
id: media.id,
|
||||
score: score * 10
|
||||
}
|
||||
await alRequest(variables)
|
||||
media = (await alRequest({ method: 'SearchIDSingle', id: media.id })).data.Media
|
||||
await anilistClient.entry(variables)
|
||||
media = (await anilistClient.searchIDSingle({ id: media.id })).data.Media
|
||||
}
|
||||
const trailer = getContext('trailer')
|
||||
function viewTrailer (media) {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import { since } from '@/modules/util'
|
||||
import { click } from '@/modules/click.js'
|
||||
import { getEpisodeNumberByAirDate } from '@/modules/providers/tosho.js'
|
||||
import { alRequest } from '@/modules/anilist'
|
||||
import { anilistClient } from '@/modules/anilist'
|
||||
|
||||
export let media
|
||||
|
||||
|
|
@ -24,10 +24,11 @@
|
|||
async function load () {
|
||||
const res = await fetch('https://api.ani.zip/mappings?anilist_id=' + id)
|
||||
const { episodes, specialCount, episodeCount } = await res.json()
|
||||
/** @type {{ airingAt: number; episode: number; }[]} */
|
||||
let alEpisodes = episodeList
|
||||
|
||||
if (!(media.episodes && media.episodes === episodeCount && media.status === 'FINISHED')) {
|
||||
const settled = (await alRequest({ method: 'Episodes', id })).data.Page?.airingSchedules
|
||||
const settled = (await anilistClient.episodes({ id })).data.Page?.airingSchedules
|
||||
if (settled?.length) alEpisodes = settled
|
||||
}
|
||||
for (const { episode, airingAt } of alEpisodes) {
|
||||
|
|
|
|||
|
|
@ -1,33 +1,31 @@
|
|||
<script>
|
||||
import { alToken } from '@/modules/settings.js'
|
||||
import { alRequest } from '@/modules/anilist.js'
|
||||
import { anilistClient } from '@/modules/anilist.js'
|
||||
import { click } from '@/modules/click.js'
|
||||
import IPC from '@/modules/ipc.js'
|
||||
export let media = null
|
||||
let following = null
|
||||
async function updateFollowing (media) {
|
||||
if (media) {
|
||||
following = null
|
||||
following = (await alRequest({ method: 'Following', id: media.id })).data?.Page?.mediaList
|
||||
}
|
||||
}
|
||||
$: updateFollowing(media)
|
||||
|
||||
/** @type {import('@/modules/al.d.ts').Media} */
|
||||
export let media
|
||||
$: following = anilistClient.following({ id: media.id })
|
||||
</script>
|
||||
|
||||
{#if following?.length && alToken}
|
||||
<div class='w-full d-flex flex-row align-items-center pt-20 mt-10'>
|
||||
<hr class='w-full' />
|
||||
<div class='font-size-18 font-weight-semi-bold px-20 text-white'>Following</div>
|
||||
<hr class='w-full' />
|
||||
</div>
|
||||
<div class='px-15 pt-5 flex-column'>
|
||||
{#each following as friend}
|
||||
<div class='d-flex align-items-center w-full pt-20 font-size-16'>
|
||||
<img src={friend.user.avatar.medium} alt='avatar' class='w-50 h-50 img-fluid rounded cover-img' />
|
||||
<span class='my-0 pl-20 mr-auto text-truncate'>{friend.user.name}</span>
|
||||
<span class='my-0 px-10 text-capitalize'>{friend.status.toLowerCase()}</span>
|
||||
<span class='material-symbols-outlined pointer text-primary font-size-18' use:click={() => IPC.emit('open', 'https://anilist.co/user/' + friend.user.name)}> open_in_new </span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{#await following then res}
|
||||
{@const following = res.data.Page.mediaList}
|
||||
{#if following?.length && alToken}
|
||||
<div class='w-full d-flex flex-row align-items-center pt-20 mt-10'>
|
||||
<hr class='w-full' />
|
||||
<div class='font-size-18 font-weight-semi-bold px-20 text-white'>Following</div>
|
||||
<hr class='w-full' />
|
||||
</div>
|
||||
<div class='px-15 pt-5 flex-column'>
|
||||
{#each following as friend}
|
||||
<div class='d-flex align-items-center w-full pt-20 font-size-16'>
|
||||
<img src={friend.user.avatar.medium} alt='avatar' class='w-50 h-50 img-fluid rounded cover-img' />
|
||||
<span class='my-0 pl-20 mr-auto text-truncate'>{friend.user.name}</span>
|
||||
<span class='my-0 px-10 text-capitalize'>{friend.status.toLowerCase()}</span>
|
||||
<span class='material-symbols-outlined pointer text-primary font-size-18' use:click={() => IPC.emit('open', 'https://anilist.co/user/' + friend.user.name)}> open_in_new </span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{/await}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { playAnime } from '../RSSView.svelte'
|
||||
import { alRequest } from '@/modules/anilist.js'
|
||||
import { anilistClient } from '@/modules/anilist.js'
|
||||
import { getMediaMaxEp } from '@/modules/anime.js'
|
||||
import { getContext } from 'svelte'
|
||||
import Details from './Details.svelte'
|
||||
|
|
@ -91,7 +91,7 @@
|
|||
</div>
|
||||
<ToggleList list={media.relations?.edges?.filter(({ node }) => node.type === 'ANIME')} let:item title='Relations'>
|
||||
<div class='w-150 mx-15 my-10 rel pointer'
|
||||
use:click={async () => { $view = null; $view = (await alRequest({ method: 'SearchIDSingle', id: item.node.id })).data.Media }}>
|
||||
use:click={async () => { $view = null; $view = (await anilistClient.searchIDSingle({ id: item.node.id })).data.Media }}>
|
||||
<img loading='lazy' src={item.node.coverImage.medium || ''} alt='cover' class='cover-img w-full h-200 rel-img rounded' />
|
||||
<div class='pt-5'>{item.relationType.replace(/_/g, ' ').toLowerCase()}</div>
|
||||
<h5 class='font-weight-bold text-white mb-5'>{item.node.title.userPreferred}</h5>
|
||||
|
|
@ -119,7 +119,7 @@
|
|||
{/if}
|
||||
<ToggleList list={media.recommendations.edges.filter(edge => edge.node.mediaRecommendation)} let:item title='Recommendations'>
|
||||
<div class='w-150 mx-15 my-10 rel pointer'
|
||||
use:click={async () => { $view = null; $view = (await alRequest({ method: 'SearchIDSingle', id: item.node.mediaRecommendation.id })).data.Media }}>
|
||||
use:click={async () => { $view = null; $view = (await anilistClient.searchIDSingle({ id: item.node.mediaRecommendation.id })).data.Media }}>
|
||||
<img loading='lazy' src={item.node.mediaRecommendation.coverImage.medium || ''} alt='cover' class='cover-img w-full h-200 rel-img rounded' />
|
||||
<h5 class='font-weight-bold text-white mb-5'>{item.node.mediaRecommendation.title.userPreferred}</h5>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import { getMediaMaxEp, formatMap, playMedia, setStatus } from '@/modules/anime.js'
|
||||
import { playAnime } from '../RSSView.svelte'
|
||||
import { toast } from 'svelte-sonner'
|
||||
import { alRequest } from '@/modules/anilist.js'
|
||||
import { anilistClient } from '@/modules/anilist.js'
|
||||
import { click } from '@/modules/click.js'
|
||||
import Details from './Details.svelte'
|
||||
import EpisodeList from './EpisodeList.svelte'
|
||||
|
|
@ -48,19 +48,12 @@
|
|||
const res = await setStatus('PLANNING', {}, media)
|
||||
media.mediaListEntry = res.data.SaveMediaListEntry
|
||||
} else {
|
||||
// delete
|
||||
alRequest({
|
||||
method: 'Delete',
|
||||
id: media.mediaListEntry.id
|
||||
})
|
||||
anilistClient.delete({ id: media.mediaListEntry.id })
|
||||
media.mediaListEntry = undefined
|
||||
}
|
||||
}
|
||||
function toggleFavourite () {
|
||||
alRequest({
|
||||
method: 'Favourite',
|
||||
id: media.id
|
||||
})
|
||||
anilistClient.favourite({ id: media.id })
|
||||
media.isFavourite = !media.isFavourite
|
||||
}
|
||||
function copyToClipboard (text) {
|
||||
|
|
@ -166,7 +159,7 @@
|
|||
</div>
|
||||
<ToggleList list={media.relations?.edges?.filter(({ node }) => node.type === 'ANIME')} let:item title='Relations'>
|
||||
<div class='w-150 mx-15 my-10 rel pointer'
|
||||
use:click={async () => { $view = null; $view = (await alRequest({ method: 'SearchIDSingle', id: item.node.id })).data.Media }}>
|
||||
use:click={async () => { $view = null; $view = (await anilistClient.searchIDSingle({ id: item.node.id })).data.Media }}>
|
||||
<img loading='lazy' src={item.node.coverImage.medium || ''} alt='cover' class='cover-img w-full h-200 rel-img rounded' />
|
||||
<div class='pt-5'>{item.relationType.replace(/_/g, ' ').toLowerCase()}</div>
|
||||
<h5 class='font-weight-bold text-white mb-5'>{item.node.title.userPreferred}</h5>
|
||||
|
|
@ -175,7 +168,7 @@
|
|||
<Following {media} />
|
||||
<ToggleList list={media.recommendations.edges.filter(edge => edge.node.mediaRecommendation)} let:item title='Recommendations'>
|
||||
<div class='w-150 mx-15 my-10 rel pointer'
|
||||
use:click={async () => { $view = null; $view = (await alRequest({ method: 'SearchIDSingle', id: item.node.mediaRecommendation.id })).data.Media }}>
|
||||
use:click={async () => { $view = null; $view = (await anilistClient.searchIDSingle({ id: item.node.mediaRecommendation.id })).data.Media }}>
|
||||
<img loading='lazy' src={item.node.mediaRecommendation.coverImage.medium || ''} alt='cover' class='cover-img w-full h-200 rel-img rounded' />
|
||||
<h5 class='font-weight-bold text-white mb-5'>{item.node.mediaRecommendation.title.userPreferred}</h5>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script context='module'>
|
||||
import { writable } from 'simple-store-svelte'
|
||||
import { alID } from '@/modules/anilist.js'
|
||||
import { anilistClient } from '@/modules/anilist.js'
|
||||
import { add, client } from '@/modules/torrent.js'
|
||||
import { generateRandomHexCode } from '@/modules/util.js'
|
||||
import { toast } from 'svelte-sonner'
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
p2pt.on('peerconnect', async peer => {
|
||||
console.log(peer.id)
|
||||
console.log('connect')
|
||||
const user = (await alID)?.data?.Viewer || {}
|
||||
const user = (await anilistClient.userID)?.data?.Viewer || {}
|
||||
p2pt.send(peer,
|
||||
JSON.stringify({
|
||||
type: 'init',
|
||||
|
|
|
|||
Loading…
Reference in a new issue