mirror of
https://github.com/ThaUnknown/miru.git
synced 2026-03-11 22:15:35 +00:00
wip: initial kitsu impl
This commit is contained in:
parent
7ba810057e
commit
db2b0a738a
8 changed files with 212 additions and 7 deletions
5
.vscode/extensions.json
vendored
5
.vscode/extensions.json
vendored
|
|
@ -4,8 +4,9 @@
|
|||
"dbaeumer.vscode-eslint",
|
||||
"GraphQL.vscode-graphql-syntax",
|
||||
"YoavBls.pretty-ts-errors",
|
||||
"svelte.svelte-vscode",
|
||||
"svelte.svelte-vscode", // 109.5.2 or older NOT NEWER
|
||||
"ardenivanov.svelte-intellisense",
|
||||
"Gruntfuggly.todo-tree"
|
||||
"Gruntfuggly.todo-tree",
|
||||
"bradlc.vscode-tailwindcss"
|
||||
]
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "ui",
|
||||
"version": "6.3.15",
|
||||
"version": "6.3.16",
|
||||
"license": "BUSL-1.1",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@9.14.4",
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
ident = { nick: 'Guest-' + crypto.randomUUID().slice(0, 6), id: crypto.randomUUID().slice(0, 6), pfpid: '0', type: 'guest' }
|
||||
}
|
||||
|
||||
if (!irc.value) irc.value = MessageClient.new(ident)
|
||||
irc.value ??= MessageClient.new(ident)
|
||||
|
||||
let message = ''
|
||||
let rows = 1
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@ export function normalizeTracks (_tracks: Track[]) {
|
|||
}
|
||||
})
|
||||
return lang.reduce<Record<string, typeof lang>>((acc, track) => {
|
||||
if (!acc[track.language]) acc[track.language] = []
|
||||
acc[track.language] ??= []
|
||||
acc[track.language]!.push(track)
|
||||
return acc
|
||||
}, {})
|
||||
|
|
@ -174,7 +174,7 @@ export function normalizeSubs (_tracks?: Record<number | string, { meta: { langu
|
|||
name: meta.name ?? meta.language ?? (!hasEng ? 'eng' : 'unk')
|
||||
}))
|
||||
return lang.reduce<Record<string, typeof lang>>((acc, track) => {
|
||||
if (!acc[track.language]) acc[track.language] = []
|
||||
acc[track.language] ??= []
|
||||
acc[track.language]!.push(track)
|
||||
return acc
|
||||
}, {})
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export default new class AuthAggregator {
|
|||
return () => unsub.forEach(fn => fn())
|
||||
})
|
||||
|
||||
syncSettings = persisted('syncSettings', { al: true, local: true })
|
||||
syncSettings = persisted('syncSettings', { al: true, local: true, kitsu: true, mal: true })
|
||||
// AUTH
|
||||
|
||||
anilist () {
|
||||
|
|
|
|||
8
src/lib/modules/auth/kitsu-types.d.ts
vendored
Normal file
8
src/lib/modules/auth/kitsu-types.d.ts
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
export interface KitsuOAuth {
|
||||
access_token: string
|
||||
created_at: number
|
||||
expires_in: number // Seconds until the access_token expires (30 days default)
|
||||
refresh_token: string
|
||||
scope: string
|
||||
token_type: string
|
||||
}
|
||||
195
src/lib/modules/auth/kitsu.ts
Normal file
195
src/lib/modules/auth/kitsu.ts
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
import { readable, writable } from 'simple-store-svelte'
|
||||
|
||||
import type { Media } from '../anilist'
|
||||
import type { KitsuOAuth } from './kitsu-types'
|
||||
import type { Entry } from '../anilist/queries'
|
||||
import type { VariablesOf } from 'gql.tada'
|
||||
|
||||
import { safeLocalStorage } from '$lib/utils'
|
||||
|
||||
const ENDPOINTS = {
|
||||
API_OAUTH: 'https://kitsu.app/api/oauth/token',
|
||||
API_USER_FETCH: 'https://kitsu.app/api/edge/users',
|
||||
API_USER_LIBRARY: 'https://kitsu.app/api/edge/library-entries'
|
||||
}
|
||||
|
||||
export default new class KitsuSync {
|
||||
auth = writable<KitsuOAuth | undefined>(safeLocalStorage('kitsuAuth'))
|
||||
viewer = writable<{id: number} | undefined>(safeLocalStorage('kitsuViewer'))
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
async _request (url: string, method: string, body?: any): Promise<any> {
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
method,
|
||||
headers: {
|
||||
'Content-Type': 'application/vnd.api+json',
|
||||
Accept: 'application/vnd.api+json',
|
||||
Authorization: this.auth.value ? `Bearer ${this.auth.value.access_token}` : ''
|
||||
},
|
||||
body: JSON.stringify(body)
|
||||
})
|
||||
|
||||
if (res.status === 403 && !body?.refresh_token) {
|
||||
await this._refresh()
|
||||
return await this._request(url, method, body)
|
||||
}
|
||||
|
||||
return await res.json()
|
||||
} catch (error) {
|
||||
// TODO: :^)
|
||||
const err = error as Error
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async _refresh () {
|
||||
try {
|
||||
const data = await this._request(
|
||||
ENDPOINTS.API_OAUTH,
|
||||
'POST',
|
||||
{
|
||||
grant_type: 'refresh_token',
|
||||
refresh_token: this.auth.value?.refresh_token
|
||||
}
|
||||
)
|
||||
this.viewer.value = data
|
||||
await this._user()
|
||||
} catch (error) {
|
||||
this.viewer.value = undefined
|
||||
}
|
||||
}
|
||||
|
||||
async _login (username: string, password: string) {
|
||||
const data = await this._request(
|
||||
ENDPOINTS.API_OAUTH,
|
||||
'POST',
|
||||
{
|
||||
grant_type: 'password',
|
||||
username,
|
||||
password
|
||||
}
|
||||
)
|
||||
|
||||
this.viewer.value = data
|
||||
await this._user()
|
||||
}
|
||||
|
||||
async _user () {
|
||||
const data = await this._request(
|
||||
ENDPOINTS.API_USER_FETCH,
|
||||
'GET',
|
||||
{ 'filter[self]': true }
|
||||
)
|
||||
|
||||
const [user] = data
|
||||
|
||||
return user.id
|
||||
}
|
||||
|
||||
async _getEntry (id: number) {
|
||||
const data = await this._request(
|
||||
ENDPOINTS.API_USER_LIBRARY,
|
||||
'GET',
|
||||
{
|
||||
'filter[animeId]': id,
|
||||
'filter[userId]': this.viewer.value?.id,
|
||||
'filter[kind]': 'anime'
|
||||
}
|
||||
)
|
||||
|
||||
const [anime] = data.data
|
||||
|
||||
return anime
|
||||
}
|
||||
|
||||
async _addEntry (id: number, attributes: Record<string, unknown>) {
|
||||
const data = await this._request(
|
||||
ENDPOINTS.API_USER_LIBRARY,
|
||||
'POST',
|
||||
{
|
||||
data: {
|
||||
attributes: {
|
||||
status: 'planned'
|
||||
},
|
||||
relationships: {
|
||||
anime: {
|
||||
data: {
|
||||
id,
|
||||
type: 'anime'
|
||||
}
|
||||
},
|
||||
user: {
|
||||
data: {
|
||||
id: this.viewer.value?.id,
|
||||
type: 'users'
|
||||
}
|
||||
}
|
||||
},
|
||||
type: 'library-entries'
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
async _updateEntry (id: number, attributes: Record<string, unknown>) {
|
||||
const data = await this._request(
|
||||
`${ENDPOINTS.API_USER_LIBRARY}/${id}`,
|
||||
'PATCH', {
|
||||
data: {
|
||||
id,
|
||||
attributes,
|
||||
type: 'library-entries'
|
||||
}
|
||||
}
|
||||
)
|
||||
return data
|
||||
}
|
||||
|
||||
async _deleteEntry (id: number) {
|
||||
this._request(
|
||||
`${ENDPOINTS.API_USER_LIBRARY}/${id}`,
|
||||
'DELETE'
|
||||
)
|
||||
}
|
||||
|
||||
hasAuth = readable(false)
|
||||
|
||||
id () {
|
||||
return -1
|
||||
}
|
||||
|
||||
profile () {
|
||||
}
|
||||
|
||||
// QUERIES/MUTATIONS
|
||||
|
||||
schedule () {
|
||||
}
|
||||
|
||||
toggleFav (id: number) {
|
||||
}
|
||||
|
||||
delete (id: number) {
|
||||
}
|
||||
|
||||
following (id: number) {
|
||||
}
|
||||
|
||||
planningIDs () {
|
||||
}
|
||||
|
||||
continueIDs () {
|
||||
}
|
||||
|
||||
sequelIDs () {
|
||||
}
|
||||
|
||||
watch (media: Media, progress: number) {
|
||||
}
|
||||
|
||||
entry (variables: VariablesOf<typeof Entry>) {
|
||||
}
|
||||
}()
|
||||
|
|
@ -1 +1,2 @@
|
|||
// eslint-disable-next-line no-undef
|
||||
module.exports = {}
|
||||
|
|
|
|||
Loading…
Reference in a new issue