paste a bunch of code

This commit is contained in:
ThaUnknown 2022-03-09 14:41:45 +01:00
parent 70f804ea7f
commit ce393b8fc3
5 changed files with 541 additions and 4 deletions

View file

@ -1,11 +1,13 @@
<script>
import Search from './Search.svelte'
import Section from './Section.svelte'
// TODO: add AL account detection for hiding
const sections = [
{
title: 'Continue Watching',
click: () => {},
cards: new Promise(() => {})
cards: new Promise(() => {}),
hide: true
},
{
title: 'New Releases',
@ -15,7 +17,8 @@
{
title: 'Your List',
click: () => {},
cards: new Promise(() => {})
cards: new Promise(() => {}),
hide: true
},
{
title: 'Trending Now',
@ -39,8 +42,10 @@
<div class="h-full py-10">
<Search />
<div>
{#each sections as opts, i (i)}
<Section {opts} />
{#each sections as opts (opts.title)}
{#if !opts.hide}
<Section {opts} />
{/if}
{/each}
</div>
</div>

View file

@ -0,0 +1,282 @@
/* global halfmoon */
const alID = 'TODO: add al id'
async function handleRequest (opts) {
return await fetch('https://graphql.anilist.co', opts).then(async res => {
const json = await res.json()
if (!res.ok) {
for (const error of json.errors) {
halfmoon.initStickyAlert({
content: `Failed making request to anilist!<br>${error.status} - ${error.message}`,
title: 'Search Failed',
alertType: 'alert-danger',
fillType: ''
})
console.error(error)
}
}
return json
})
}
export function alEntry (filemedia) {
if (filemedia.media && localStorage.getItem('ALtoken')) {
alRequest({ method: 'SearchIDStatus', id: filemedia.media.id }).then(res => {
if ((res.errors && res.errors[0].status === 404) || res.data.MediaList.progress <= filemedia.episodeNumber || filemedia.episodes === 1) {
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: filemedia.media.id,
status: 'CURRENT',
episode: filemedia.episodeNumber || 1
}
if (filemedia.episodeNumber === filemedia.media.episodes || filemedia.episodes === 1) {
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
})
}
handleRequest(options)
}
})
}
}
export 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'
}
}
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
},
mediaListEntry {
progress
},
source,
studios(isMain: true) {
nodes {
name
}
},
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')
switch (opts.method) {
case 'SearchName': {
variables.search = opts.name
query = `
query ($page: Int, $perPage: Int, $sort: [MediaSort], $type: MediaType, $search: String, $status: [MediaStatus]) {
Page (page: $page, perPage: $perPage) {
pageInfo {
hasNextPage
},
media(type: $type, search: $search, sort: $sort, status_in: $status) {
${queryObjects}
}
}
}`
break
} case 'SearchIDSingle': {
variables.id = opts.id
query = `
query ($id: Int, $type: MediaType) {
Media (id: $id, type: $type) {
${queryObjects}
}
}`
break
} case 'SearchIDS': {
variables.id = opts.id
query = `
query ($id: [Int], $type: MediaType, $page: Int, $perPage: Int) {
Page (page: $page, perPage: $perPage) {
pageInfo {
hasNextPage
},
media (id_in: $id, type: $type) {
${queryObjects}
}
}
}`
break
} case 'Viewer': {
query = `
query {
Viewer {
avatar {
medium
},
name,
id
}
}`
break
} case 'UserLists': {
variables.id = opts.id
query = `
query ($page: Int, $perPage: Int, $id: Int, $type: MediaType, $status_in: [MediaListStatus]){
Page (page: $page, perPage: $perPage) {
pageInfo {
hasNextPage
},
mediaList (userId: $id, type: $type, status_in: $status_in) {
media {
${queryObjects}
}
}
}
}`
break
} case 'SearchIDStatus': {
variables.id = alID
variables.mediaId = opts.id
query = `
query ($id: Int, $mediaId: Int){
MediaList(userId: $id, mediaId: $mediaId) {
status,
progress,
repeat
}
}`
break
} case '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
query = `
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}
}
}
}
}`
break
} case '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, $startDate: FuzzyDateInt) {
Page (page: $page, perPage: $perPage) {
pageInfo {
hasNextPage
},
media(type: $type, search: $search, sort: $sort, status: $status, season: $season, seasonYear: $year, genre: $genre, format: $format, startDate_greater: $startDate) {
${queryObjects}
}
}
}`
}
}
options.body = JSON.stringify({
query: query.replace(/\s/g, ''),
variables: variables
})
return await handleRequest(options)
}

View file

@ -0,0 +1,228 @@
/* global halfmoon */
import { client } from './torrent.js'
import { DOMPARSER } from './util.js'
import { alRequest } from './anilist.js'
import { nyaaRss } from './rss.js'
import anitomyscript from 'anitomyscript'
const torrentRx = /(^magnet:){1}|(^[A-F\d]{8,40}$){1}|(.*\.torrent$){1}/i
const imageRx = /\.(jpeg|jpg|gif|png|webp)/i
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 formData = new FormData()
formData.append('image', item.getAsFile())
traceAnime(formData, 'file')
} else if (item && item.type === 'text/plain') {
item.getAsString(text => {
if (torrentRx.exec(text)) {
e.preventDefault()
client.playTorrent(text)
} else if (imageRx.exec(text)) {
e.preventDefault()
traceAnime(text)
}
})
} else if (item && item.type === 'text/html') {
item.getAsString(text => {
const img = DOMPARSER(text, 'text/html').querySelectorAll('img')[0]
if (img) {
e.preventDefault()
traceAnime(img.src)
}
})
}
})
function traceAnime (image, type) { // WAIT lookup logic
if (type === 'file') {
const reader = new FileReader()
reader.onload = e => {
halfmoon.initStickyAlert({
content: `Looking up anime for image.<br><img class="w-200 rounded pt-5" src="${e.target.result}">`
})
}
reader.readAsDataURL(image.get('image'))
} else {
halfmoon.initStickyAlert({
content: `Looking up anime for image.<br><img class="w-200 rounded pt-5" src="${image}">`
})
}
let options
let url = `https://api.trace.moe/search?cutBorders&url=${image}`
if (type === 'file') {
options = {
method: 'POST',
body: image
}
url = 'https://api.trace.moe/search'
}
fetch(url, options).then(res => res.json()).then(async ({ result }) => {
if (result && result[0].similarity >= 0.85) {
// const res = await alRequest({ method: 'SearchIDSingle', id: result[0].anilist })
// viewMedia(res.data.Media, result[0].episode)
// TODO: view 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: ''
})
}
})
}
export const episodeRx = /Episode (\d+) - (.*)/
export async function nyaaSearch (media, episode, isOffline) {
if (parseInt(episode) < 10) {
episode = `0${episode}`
}
const table = document.querySelector('tbody.results')
const results = await nyaaRss(media, episode, isOffline)
if (results.children.length === 0) {
halfmoon.initStickyAlert({
content: `Couldn't find torrent for ${media.title.userPreferred} Episode ${parseInt(episode)}! Try specifying a torrent manually.`,
title: 'Search Failed',
alertType: 'alert-danger',
fillType: ''
})
} else {
table.innerHTML = ''
table.appendChild(results)
halfmoon.toggleModal('tsearch')
}
}
// resolve anime name based on file name and store it
export async function resolveFileMedia (opts) {
// opts.fileName opts.isRelease
async function resolveTitle (title) {
if (!(title in relations)) {
// resolve name and shit
const method = { name: title, method: 'SearchName', perPage: 1, status: ['RELEASING', 'FINISHED'], sort: 'SEARCH_MATCH', startDate: 10000000 }
let res = await alRequest(method)
if (!res.data.Page.media[0]) {
const index = method.name.search(/S\d/)
method.name = ((index !== -1 && method.name.slice(0, index) + method.name.slice(index + 1, method.name.length)) || method.name).replace('(TV)', '').replace(/ (19[5-9]\d|20[0-6]\d)/, '').replace('-', '')
method.status = ['RELEASING', 'FINISHED']
res = await alRequest(method)
}
if (res.data.Page.media[0]) {
relations[title] = res.data.Page.media[0].id
} else {
relations[title] = null
}
}
}
const parsePromises = opts.fileName.constructor === Array
? opts.fileName.map(name => anitomyscript(name))
: [anitomyscript(opts.fileName)]
const parseObjs = await Promise.all(parsePromises)
await Promise.all([...new Set(parseObjs.map(obj => obj.anime_title))].map(title => resolveTitle(title)))
const assoc = {}
for (let ids = [...new Set(parseObjs.map(obj => relations[obj.anime_title]))]; ids.length; ids = ids.slice(50)) {
for await (const media of (await alRequest({ method: 'SearchIDS', id: ids.slice(0, 50), perPage: 50 })).data.Page.media) {
assoc[media.id] = media
}
}
const fileMedias = []
for (const praseObj of parseObjs) {
let episode
let media = assoc[relations[praseObj.anime_title]]
async function resolveSeason (opts) {
// opts.media, opts.episode, opts.increment, opts.offset
let epMin, epMax
if (opts.episode.constructor === Array) { // support batch episode ranges
epMin = Number(opts.episode[0])
epMax = Number(opts.episode[opts.episode.length - 1])
} else {
epMin = epMax = Number(opts.episode)
}
let tempMedia, increment
if (opts.media.relations.edges.some(edge => edge.relationType === 'PREQUEL' && (edge.node.format === 'TV' || 'TV_SHORT')) && !opts.increment) {
// media has prequel and we dont want to move up in the tree
tempMedia = opts.media.relations.edges.filter(edge => edge.relationType === 'PREQUEL' && (edge.node.format === 'TV' || 'TV_SHORT'))[0].node
} else if (opts.media.relations.edges.some(edge => edge.relationType === 'SEQUEL' && (edge.node.format === 'TV' || 'TV_SHORT'))) {
// media doesnt have prequel, or we want to move up in the tree
tempMedia = opts.media.relations.edges.filter(edge => edge.relationType === 'SEQUEL' && (edge.node.format === 'TV' || 'TV_SHORT'))[0].node
increment = true
}
if (tempMedia?.episodes && epMax - (opts.offset + media.episodes) > (media.nextAiringEpisode?.episode || media.episodes)) {
// episode is still out of bounds
const nextEdge = await alRequest({ method: 'SearchIDSingle', id: tempMedia.id })
await resolveSeason({ media: nextEdge.data.Media, episode: opts.episode, offset: opts.offset + nextEdge.data.Media.episodes, increment: increment })
} else if (tempMedia?.episodes && epMax - (opts.offset + media.episodes) <= (media.nextAiringEpisode?.episode || media.episodes) && epMin - (opts.offset + media.episodes) > 0) {
// episode is in range, seems good! overwriting media to count up "seasons"
if (opts.episode.constructor === Array) {
episode = `${praseObj.episode_number[0] - (opts.offset + media.episodes)} ~ ${praseObj.episode_number[praseObj.episode_number.length - 1] - (opts.offset + media.episodes)}`
} else {
episode = opts.episode - (opts.offset + media.episodes)
}
if (opts.increment || increment) {
const nextEdge = await alRequest({ method: 'SearchIDSingle', id: tempMedia.id })
media = nextEdge.data.Media
}
} else {
console.log('error in parsing!', opts.media, tempMedia)
halfmoon.initStickyAlert({
content: `Failed resolving anime episode!<br>${opts.media.title.userPreferred} - ${epMax}`,
title: 'Parsing Error',
alertType: 'alert-secondary',
fillType: ''
})
// something failed, most likely couldnt find an edge or processing failed, force episode number even if its invalid/out of bounds, better than nothing
if (opts.episode.constructor === Array) {
episode = `${Number(praseObj.episode_number[0])} ~ ${Number(praseObj.episode_number[praseObj.episode_number.length - 1])}`
} else {
episode = Number(opts.episode)
}
}
}
// resolve episode, if movie, dont.
if ((media?.format !== 'MOVIE' || (media.episodes || media.nextAiringEpisode.episode)) && praseObj.episode_number) {
if (praseObj.episode_number.constructor === Array) {
// is an episode range
if (parseInt(praseObj.episode_number[0]) === 1) {
// if it starts with #1 and overflows then it includes more than 1 season in a batch, cant fix this cleanly, name is parsed per file basis so this shouldnt be an issue
episode = `${praseObj.episode_number[0]} ~ ${praseObj.episode_number[praseObj.episode_number.length - 1]}`
} else {
if ((media?.episodes || media?.nextAiringEpisode?.episode) && parseInt(praseObj.episode_number[praseObj.episode_number.length - 1]) > (media.episodes || media.nextAiringEpisode.episode)) {
// if highest value is bigger than episode count or latest streamed episode +1 for safety, parseint to math.floor a number like 12.5 - specials - in 1 go
await resolveSeason({ media: media, episode: praseObj.episode_number, offset: 0 })
} else {
// cant find ep count or range seems fine
episode = `${Number(praseObj.episode_number[0])} ~ ${Number(praseObj.episode_number[praseObj.episode_number.length - 1])}`
}
}
} else {
if ((media?.episodes || media?.nextAiringEpisode?.episode) && parseInt(praseObj.episode_number) > (media.episodes || media.nextAiringEpisode.episode)) {
// value bigger than episode count
await resolveSeason({ media: media, episode: praseObj.episode_number, offset: 0 })
} else {
// cant find ep count or episode seems fine
episode = Number(praseObj.episode_number)
}
}
}
const streamingEpisode = media?.streamingEpisodes.filter(episode => episodeRx.exec(episode.title) && Number(episodeRx.exec(episode.title)[1]) === Number(praseObj.episode_number))[0]
fileMedias.push({
mediaTitle: media?.title.userPreferred,
episodeNumber: episode,
episodeTitle: streamingEpisode && episodeRx.exec(streamingEpisode.title)[2],
episodeThumbnail: streamingEpisode?.thumbnail,
mediaCover: media?.coverImage.medium,
name: 'Miru',
parseObject: praseObj,
media: media
})
}
return fileMedias.length === 1 ? fileMedias[0] : fileMedias
}
export const relations = JSON.parse(localStorage.getItem('relations')) || {}

View file

@ -0,0 +1 @@
export const client = null

View file

@ -1,3 +1,20 @@
/* global halfmoon */
halfmoon.showModal = id => {
const t = document.getElementById(id)
t && t.classList.add('show')
}
halfmoon.hideModal = id => {
const t = document.getElementById(id)
t && t.classList.remove('show')
}
// export const searchParams = new URLSearchParams(location.href)
// if (searchParams.get('access_token')) {
// localStorage.setItem('ALtoken', searchParams.get('access_token'))
// window.location = '/app/#home'
// }
export function countdown (s) {
const d = Math.floor(s / (3600 * 24))
s -= d * 3600 * 24
@ -11,3 +28,7 @@ export function countdown (s) {
if (d || h || m) tmp.push(m + 'm')
return tmp.join(' ')
}
export const DOMPARSER = new DOMParser().parseFromString.bind(new DOMParser())
export const sleep = t => new Promise(resolve => setTimeout(resolve, t))