const torrentRx = /(magnet:){1}|(^[A-F\d]{8,40}$){1}|(.*\.torrent){1}/i
const imageRx = /\.(jpeg|jpg|gif|png|webp)/
window.addEventListener('paste', async e => { // WAIT image lookup on paste, or add torrent on paste
const item = e.clipboardData.items[0]
if (item && item.type.indexOf('image') === 0) {
e.preventDefault()
const reader = new FileReader()
reader.onload = e => {
traceAnime(e.target.result, 'uri')
}
reader.readAsDataURL(item.getAsFile())
} else if (item && item.type === 'text/plain') {
item.getAsString(text => {
if (torrentRx.exec(text)) {
e.preventDefault()
search.value = ''
client.addTorrent(text, {})
} else if (imageRx.exec(text)) {
e.preventDefault()
search.value = ''
traceAnime(text)
}
})
} else if (item && item.type === 'text/html') {
item.getAsString(text => {
const img = new DOMParser().parseFromString(text, 'text/html').querySelectorAll('img')[0]
if (img) {
e.preventDefault()
search.value = ''
traceAnime(img.src)
}
})
}
})
if (searchParams.get('link')) {
traceAnime(searchParams.get('link'))
window.location = '/app/#home'
}
function traceAnime (image, type) { // WAIT lookup logic
halfmoon.initStickyAlert({
content: `Looking up anime for image.`
})
let options
let url = `https://trace.moe/api/search?url=${image}`
if (type === 'uri') {
options = {
method: 'POST',
body: JSON.stringify({ image: image }),
headers: { 'Content-Type': 'application/json' }
}
url = 'https://trace.moe/api/search'
}
fetch(url, options).then((res) => res.json())
.then(async (result) => {
if (result.docs[0].similarity >= 0.85) {
const res = await alRequest({ method: 'SearchIDSingle', id: result.docs[0].anilist_id })
viewAnime(res.data.Media)
} else {
halfmoon.initStickyAlert({
content: 'Couldn\'t find anime for specified image! Try to remove black bars, or use a more detailed image.',
title: 'Search Failed',
alertType: 'alert-danger',
fillType: ''
})
}
})
}
// events
navNowPlaying.onclick = () => { viewAnime(client.nowPlaying?.media) }
// AL lookup logic
async function alRequest (opts) {
let query
const variables = {
type: 'ANIME',
sort: opts.sort || 'TRENDING_DESC',
page: opts.page || 1,
perPage: opts.perPage || 30,
status_in: opts.status_in || '[CURRENT,PLANNING]',
chunk: opts.chunk || 1,
perchunk: opts.perChunk || 30
}
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json'
},
body: undefined
}
const queryObjects = `
id
title {
romaji
english
native
userPreferred
}
description(
asHtml: true
)
season
seasonYear
format
status
episodes
duration
averageScore
genres
coverImage {
extraLarge
medium
color
}
countryOfOrigin
isAdult
bannerImage
synonyms
nextAiringEpisode {
timeUntilAiring
episode
}
trailer {
id
site
}
streamingEpisodes {
title
thumbnail
}
relations {
edges {
relationType(version:2)
node {
id
title {
userPreferred
}
coverImage {
medium
}
type
status
format
episodes
}
}
}`
if (opts.status) variables.status = opts.status
if (localStorage.getItem('ALtoken')) options.headers.Authorization = localStorage.getItem('ALtoken')
if (opts.method === 'SearchName') { // look at me go, i'm doing the yandree dev
variables.search = opts.name
query = `
query ($page: Int, $perPage: Int, $sort: [MediaSort], $type: MediaType, $search: String, $status: MediaStatus) {
Page (page: $page, perPage: $perPage) {
media(type: $type, search: $search, sort: $sort, status: $status) {
${queryObjects}
}
}
}`
} else if (opts.method === 'SearchIDSingle') {
variables.id = opts.id
query = `
query ($id: Int, $type: MediaType) {
Media (id: $id, type: $type){
${queryObjects}
}
}`
} else if (opts.method === 'Viewer') {
query = `
query {
Viewer {
avatar {
medium
},
name,
id
}
}`
} else if (opts.method === 'UserLists') {
variables.id = opts.id
query = `
query ($page: Int, $perPage: Int, $id: Int, $type: MediaType, $status_in: [MediaListStatus]){
Page (page: $page, perPage: $perPage) {
mediaList (userId: $id, type: $type, status_in: $status_in) {
media {
${queryObjects}
}
}
}
}`
} else if (opts.method === 'SearchIDStatus') {
variables.id = alID
variables.mediaId = opts.id
query = `
query ($id: Int, $mediaId: Int){
MediaList(userId: $id, mediaId: $mediaId) {
status
progress
repeat
}
}`
} else if (opts.method === 'AiringSchedule') {
const date = new Date()
const diff = date.getDay() >= 1 ? date.getDay() - 1 : 6 - date.getDay()
date.setDate(date.getDate() - diff)
date.setHours(0, 0, 0, 0)
variables.from = date.getTime() / 1000
variables.to = (date.getTime() + 7 * 24 * 60 * 60 * 1000) / 1000
console.log(variables)
query = `
query ($page: Int, $perPage: Int, $from: Int, $to: Int) {
Page (page: $page, perPage: $perPage) {
airingSchedules(airingAt_greater: $from, airingAt_lesser: $to) {
episode
timeUntilAiring
airingAt
media{
${queryObjects}
}
}
}
}`
} else if (opts.method === 'Search') {
variables.genre = opts.genre
variables.search = opts.search
variables.year = opts.year
variables.season = opts.season
variables.format = opts.format
variables.status = opts.status
variables.sort = opts.sort || 'SEARCH_MATCH'
query = `
query ($page: Int, $perPage: Int, $sort: [MediaSort], $type: MediaType, $search: String, $status: MediaStatus, $season: MediaSeason, $year: Int, $genre: String, $format: MediaFormat) {
Page (page: $page, perPage: $perPage) {
media(type: $type, search: $search, sort: $sort, status: $status, season: $season, seasonYear: $year, genre: $genre, format: $format) {
${queryObjects}
}
}
}`
}
options.body = JSON.stringify({
query: query.replace(/\s{2,}/g, ' '),
variables: variables
})
const res = await fetch('https://graphql.anilist.co', options).catch((error) => console.error(error))
const json = await res.json()
console.log(json)
return json
}
async function alEntry () {
if (client.nowPlaying.media && localStorage.getItem('ALtoken')) {
const res = await alRequest({ method: 'SearchIDStatus', id: client.nowPlaying.media.id })
if ((res.errors && res.errors[0].status === 404) || res.data.MediaList.progress <= client.nowPlaying.episodeNumber) {
const query = `
mutation ($id: Int, $status: MediaListStatus, $episode: Int, $repeat: Int) {
SaveMediaListEntry (mediaId: $id, status: $status, progress: $episode, repeat: $repeat) {
id
status
progress
repeat
}
}`
const variables = {
repeat: 0,
id: client.nowPlaying.media.id,
status: 'CURRENT',
episode: client.nowPlaying.episodeNumber
}
if (client.nowPlaying.episodeNumber === client.nowPlaying.media.episodes) {
variables.status = 'COMPLETED'
if (res.data.MediaList.status === 'COMPLETED') {
variables.repeat = res.data.MediaList.repeat + 1
}
}
const options = {
method: 'POST',
headers: {
Authorization: 'Bearer ' + localStorage.getItem('ALtoken'),
'Content-Type': 'application/json',
Accept: 'application/json'
},
body: JSON.stringify({
query: query,
variables: variables
})
}
fetch('https://graphql.anilist.co', options).catch((error) => console.error(error))
}
}
}
// these really shouldnt be global
const detailsfrag = document.createDocumentFragment()
const details = {
averageScore: 'Average Score',
// duration: "Episode Duration",
// episodes: "Episodes",
// format: "Format",
genres: 'Genres',
// season: "Season",
// seasonYear: "Year",
status: 'Status',
english: 'English',
romaji: 'Romaji',
native: 'Native',
synonyms: 'Synonyms'
}
const episodeRx = /Episode (\d+) - (.*)/
// this is fucked beyond belief, this is why you use frameworks
function viewAnime (media) {
halfmoon.showModal('view')
view.setAttribute('style', `background-image: url(${media.bannerImage}) !important`)
viewImg.src = media.coverImage.extraLarge
viewTitle.innerHTML = media.title.userPreferred
viewDesc.innerHTML = media.description || ''
viewDetails.innerHTML = ''
detailsCreator(media)
viewDetails.appendChild(detailsfrag)
if (media.nextAiringEpisode) {
const temp = document.createElement('p')
temp.innerHTML = `Airing
Episode ${media.nextAiringEpisode.episode}: ${countdown(media.nextAiringEpisode.timeUntilAiring)}`
viewDetails.prepend(temp)
}
viewSeason.innerHTML = `${(media.season ? media.season.toLowerCase() + ' ' : '') + (media.seasonYear ? media.seasonYear : '')}`
viewMediaInfo.innerHTML = `${media.format ? '' + media.format + '' : ''}${media.episodes ? '' + media.episodes + ' Episodes' : ''}${media.duration ? '' + media.duration + ' Minutes' : ''}`
viewPlay.onclick = () => { nyaaSearch(media, 1); halfmoon.toggleModal('view') }
if (media.trailer) {
viewTrailer.removeAttribute('disabled', '')
viewTrailer.onclick = () =>
trailerPopup(media.trailer)
} else {
viewTrailer.setAttribute('disabled', '')
}
if (media.status === 'NOT_YET_RELEASED') {
viewPlay.setAttribute('disabled', '')
} else {
viewPlay.removeAttribute('disabled', '')
}
if (media.relations.edges.length) {
viewRelationsGallery.classList.remove('d-none')
viewRelationsGallery.innerHTML = ''
const frag = document.createDocumentFragment()
media.relations.edges.forEach(edge => {
const template = document.createElement('div')
template.classList.add('card', 'm-0', 'p-0')
template.innerHTML = `
${edge.node.title.userPreferred}
${edge.relationType.toLowerCase()}