ani-auto 1.4.4 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/anime/LICENSE +21 -0
- package/anime/api/index.js +110 -0
- package/anime/app.js +79 -0
- package/anime/controllers/animeInfoController.js +48 -0
- package/anime/controllers/animeListController.js +21 -0
- package/anime/controllers/homeController.js +42 -0
- package/anime/controllers/playController.js +140 -0
- package/anime/controllers/queueController.js +20 -0
- package/anime/controllers/testController.js +667 -0
- package/anime/index.js +139 -0
- package/anime/middleware/cache.js +64 -0
- package/anime/middleware/errorHandler.js +33 -0
- package/anime/middleware/rateLimiter.js +73 -0
- package/anime/models/animeInfoModel.js +193 -0
- package/anime/models/animeListModel.js +69 -0
- package/anime/models/homeModel.js +33 -0
- package/anime/models/playModel.js +528 -0
- package/anime/models/queueModel.js +22 -0
- package/anime/package.json +27 -0
- package/anime/pnpm-lock.yaml +1774 -0
- package/anime/readme.md +236 -0
- package/anime/routes/animeInfoRoutes.js +9 -0
- package/anime/routes/animeListRoutes.js +9 -0
- package/anime/routes/homeRoutes.js +10 -0
- package/anime/routes/playRoutes.js +11 -0
- package/anime/routes/queueRoutes.js +8 -0
- package/anime/routes/testRoutes.js +325 -0
- package/anime/routes/webhookRoutes.js +23 -0
- package/anime/scrapers/animepahe.js +1053 -0
- package/anime/test-server.js +10 -0
- package/anime/test.sh +275 -0
- package/anime/utils/browser.js +172 -0
- package/anime/utils/config.js +209 -0
- package/anime/utils/dataProcessor.js +172 -0
- package/anime/utils/diskCache.js +228 -0
- package/anime/utils/flaresolverr.js +361 -0
- package/anime/utils/jsParser.js +19 -0
- package/anime/utils/redis.js +64 -0
- package/anime/utils/requestManager.js +706 -0
- package/anime/utils/urlConverter.js +88 -0
- package/anime/utils/webhookNotifier.js +190 -0
- package/cookiereader.py +291 -0
- package/package.json +14 -6
- package/src/anilist.js +15 -2
- package/src/api/anilist.js +32 -0
- package/src/api/anime.js +181 -0
- package/src/api/cf-bypass.js +131 -0
- package/src/api/config.js +134 -0
- package/src/api/daemon.js +62 -0
- package/src/api/download.js +98 -0
- package/src/api/match.js +58 -0
- package/src/api/runs.js +15 -0
- package/src/api/update.js +96 -0
- package/src/cli.js +18 -2
- package/src/config.js +14 -2
- package/src/daemon.js +1 -1
- package/src/db.js +38 -11
- package/src/engine.js +62 -11
- package/src/scraper.js +2 -2
- package/src/server.js +108 -0
- package/utils.js +21 -4
- package/web/index.html +13 -0
- package/web/package-lock.json +1420 -0
- package/web/package.json +24 -0
- package/web/src/App.vue +200 -0
- package/web/src/api/client.js +75 -0
- package/web/src/assets/styles/base.css +961 -0
- package/web/src/components/UpdateModal.vue +157 -0
- package/web/src/components/sidebar/SidebarResizable.vue +170 -0
- package/web/src/components/sidebar/sidebarConfig.js +24 -0
- package/web/src/main.js +104 -0
- package/web/src/router/index.js +44 -0
- package/web/src/stores/config.js +27 -0
- package/web/src/stores/download.js +107 -0
- package/web/src/stores/update.js +73 -0
- package/web/src/views/About.vue +42 -0
- package/web/src/views/AniListSync.vue +122 -0
- package/web/src/views/AnimeDetail.vue +202 -0
- package/web/src/views/AnimeList.vue +110 -0
- package/web/src/views/CFResult.vue +312 -0
- package/web/src/views/DaemonControl.vue +83 -0
- package/web/src/views/Dashboard.vue +187 -0
- package/web/src/views/DownloadCenter.vue +153 -0
- package/web/src/views/MatchFinder.vue +131 -0
- package/web/src/views/OAuthCallback.vue +44 -0
- package/web/src/views/Onboarding.vue +93 -0
- package/web/src/views/RunHistory.vue +122 -0
- package/web/src/views/Settings.vue +410 -0
- package/web/vite.config.js +23 -0
package/web/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ani-auto-web",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "vite build",
|
|
9
|
+
"preview": "vite preview"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@hugeicons/core-free-icons": "^4.2.2",
|
|
13
|
+
"@hugeicons/vue": "^1.0.7",
|
|
14
|
+
"@primeuix/themes": "^2.0.3",
|
|
15
|
+
"pinia": "^2.1.0",
|
|
16
|
+
"primevue": "^4.5.5",
|
|
17
|
+
"vue": "^3.4.0",
|
|
18
|
+
"vue-router": "^4.3.0"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@vitejs/plugin-vue": "^5.0.0",
|
|
22
|
+
"vite": "^5.4.0"
|
|
23
|
+
}
|
|
24
|
+
}
|
package/web/src/App.vue
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div v-if="isFullLayout" id="app">
|
|
3
|
+
<router-view />
|
|
4
|
+
</div>
|
|
5
|
+
<div v-else id="app">
|
|
6
|
+
<Toast position="top-right" />
|
|
7
|
+
<Toast position="top-left" />
|
|
8
|
+
<div class="sidebar-overlay" v-if="mobileSidebarOpen" @click="closeMobileSidebar"></div>
|
|
9
|
+
<SidebarResizable
|
|
10
|
+
:collapsed="collapsed"
|
|
11
|
+
:width="sidebarWidth"
|
|
12
|
+
:mobileOpen="mobileSidebarOpen"
|
|
13
|
+
@update:collapsed="collapsed = $event"
|
|
14
|
+
@update:width="sidebarWidth = $event"
|
|
15
|
+
@navigate="closeMobileSidebar"
|
|
16
|
+
/>
|
|
17
|
+
<div class="main-column" :style="mainColumnStyle">
|
|
18
|
+
<header class="top-bar">
|
|
19
|
+
<div class="top-bar-left">
|
|
20
|
+
<button class="hamburger" @click="toggleMobileSidebar" aria-label="Toggle navigation">
|
|
21
|
+
<HugeiconsIcon :icon="Menu01Icon" :size="20" />
|
|
22
|
+
</button>
|
|
23
|
+
<span class="top-bar-label">{{ pageLabel }}</span>
|
|
24
|
+
</div>
|
|
25
|
+
<div class="top-bar-right">
|
|
26
|
+
<span
|
|
27
|
+
v-if="updateStore.updateAvailable"
|
|
28
|
+
class="update-pill"
|
|
29
|
+
@click="showUpdateModal = true"
|
|
30
|
+
>Update v{{ updateStore.latestVersion }}</span>
|
|
31
|
+
<span class="top-bar-time">{{ topBarStatus }}</span>
|
|
32
|
+
</div>
|
|
33
|
+
</header>
|
|
34
|
+
<main class="main">
|
|
35
|
+
<router-view />
|
|
36
|
+
</main>
|
|
37
|
+
</div>
|
|
38
|
+
<UpdateModal :visible="showUpdateModal" @close="onModalClose" />
|
|
39
|
+
</div>
|
|
40
|
+
</template>
|
|
41
|
+
|
|
42
|
+
<script setup>
|
|
43
|
+
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
|
|
44
|
+
import { useRoute } from 'vue-router'
|
|
45
|
+
import { useConfigStore } from './stores/config'
|
|
46
|
+
import { useDownloadStore } from './stores/download'
|
|
47
|
+
import { useUpdateStore } from './stores/update'
|
|
48
|
+
import { useToast } from 'primevue/usetoast'
|
|
49
|
+
import { HugeiconsIcon } from '@hugeicons/vue'
|
|
50
|
+
import { Menu01Icon } from '@hugeicons/core-free-icons'
|
|
51
|
+
import SidebarResizable from './components/sidebar/SidebarResizable.vue'
|
|
52
|
+
import UpdateModal from './components/UpdateModal.vue'
|
|
53
|
+
|
|
54
|
+
const route = useRoute()
|
|
55
|
+
const configStore = useConfigStore()
|
|
56
|
+
const downloadStore = useDownloadStore()
|
|
57
|
+
const updateStore = useUpdateStore()
|
|
58
|
+
const toast = useToast()
|
|
59
|
+
|
|
60
|
+
const isFullLayout = computed(() => route.meta?.layout === 'full')
|
|
61
|
+
|
|
62
|
+
const pageLabels = {
|
|
63
|
+
'dashboard': 'Dashboard',
|
|
64
|
+
'onboarding': 'Onboarding',
|
|
65
|
+
'oauth-callback': 'OAuth',
|
|
66
|
+
'anime': 'Anime',
|
|
67
|
+
'anime-detail': 'Anime',
|
|
68
|
+
'anilist-sync': 'AniList Sync',
|
|
69
|
+
'match': 'Match Finder',
|
|
70
|
+
'download': 'Download Center',
|
|
71
|
+
'settings': 'Settings',
|
|
72
|
+
'daemon': 'Daemon Control',
|
|
73
|
+
'runs': 'Run History',
|
|
74
|
+
'about': 'About',
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const pageLabel = computed(() => pageLabels[route.name] || 'ani-auto')
|
|
78
|
+
|
|
79
|
+
const topBarStatus = computed(() => {
|
|
80
|
+
const ds = downloadStore
|
|
81
|
+
if (ds.running) {
|
|
82
|
+
const active = Object.keys(ds.progress).length
|
|
83
|
+
return active ? `${active} downloading` : 'Starting...'
|
|
84
|
+
}
|
|
85
|
+
return 'Idle'
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
const showUpdateModal = ref(false)
|
|
89
|
+
|
|
90
|
+
// Resizable sidebar
|
|
91
|
+
const collapsed = ref(false)
|
|
92
|
+
const sidebarWidth = ref(200)
|
|
93
|
+
const lastExpandedWidth = ref(200)
|
|
94
|
+
const STORAGE_KEY = 'ani-auto-sidebar-w'
|
|
95
|
+
|
|
96
|
+
const mainColumnStyle = computed(() => {
|
|
97
|
+
if (isMobile.value) return {}
|
|
98
|
+
return { marginLeft: collapsed.value ? '56px' : sidebarWidth.value + 'px' }
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
// Mobile sidebar
|
|
102
|
+
const mobileSidebarOpen = ref(false)
|
|
103
|
+
const isMobile = ref(false)
|
|
104
|
+
|
|
105
|
+
function checkMobile() {
|
|
106
|
+
isMobile.value = window.innerWidth < 768
|
|
107
|
+
if (!isMobile.value) mobileSidebarOpen.value = false
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function toggleMobileSidebar() {
|
|
111
|
+
mobileSidebarOpen.value = !mobileSidebarOpen.value
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function closeMobileSidebar() {
|
|
115
|
+
mobileSidebarOpen.value = false
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function onModalClose() {
|
|
119
|
+
showUpdateModal.value = false
|
|
120
|
+
updateStore.reset()
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
watch(() => updateStore.updateAvailable, (available) => {
|
|
124
|
+
if (available) {
|
|
125
|
+
toast.add({
|
|
126
|
+
severity: 'info',
|
|
127
|
+
summary: 'Update Available',
|
|
128
|
+
detail: `v${updateStore.latestVersion}`,
|
|
129
|
+
group: 'top-left',
|
|
130
|
+
life: 6000,
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
watch(() => updateStore.installDone, (done) => {
|
|
136
|
+
if (done) {
|
|
137
|
+
toast.add({
|
|
138
|
+
severity: 'success',
|
|
139
|
+
summary: 'Update Complete',
|
|
140
|
+
detail: updateStore.installMessage,
|
|
141
|
+
group: 'top-left',
|
|
142
|
+
life: 8000,
|
|
143
|
+
})
|
|
144
|
+
}
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
watch(() => updateStore.installError, (err) => {
|
|
148
|
+
if (err) {
|
|
149
|
+
toast.add({
|
|
150
|
+
severity: 'error',
|
|
151
|
+
summary: 'Update Failed',
|
|
152
|
+
detail: err,
|
|
153
|
+
group: 'top-left',
|
|
154
|
+
life: 10000,
|
|
155
|
+
})
|
|
156
|
+
}
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
onMounted(async () => {
|
|
160
|
+
checkMobile()
|
|
161
|
+
window.addEventListener('resize', checkMobile)
|
|
162
|
+
|
|
163
|
+
const saved = localStorage.getItem(STORAGE_KEY)
|
|
164
|
+
if (saved) {
|
|
165
|
+
const parsed = JSON.parse(saved)
|
|
166
|
+
sidebarWidth.value = parsed.width || 200
|
|
167
|
+
collapsed.value = parsed.collapsed || false
|
|
168
|
+
lastExpandedWidth.value = parsed.lastExpanded || 200
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (!isFullLayout.value) {
|
|
172
|
+
await configStore.fetch()
|
|
173
|
+
if (configStore.isConfigured) {
|
|
174
|
+
downloadStore.connectWebSocket()
|
|
175
|
+
downloadStore.onEvent = (msg) => {
|
|
176
|
+
if (msg.type?.startsWith('update:')) {
|
|
177
|
+
updateStore.handleEvent(msg)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (configStore.config?.autoUpdateCheck !== false) {
|
|
181
|
+
updateStore.check()
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
watch([collapsed, sidebarWidth], () => {
|
|
188
|
+
if (isMobile.value) return
|
|
189
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify({
|
|
190
|
+
width: sidebarWidth.value,
|
|
191
|
+
collapsed: collapsed.value,
|
|
192
|
+
lastExpanded: lastExpandedWidth.value,
|
|
193
|
+
}))
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
onUnmounted(() => {
|
|
197
|
+
window.removeEventListener('resize', checkMobile)
|
|
198
|
+
downloadStore.onEvent = null
|
|
199
|
+
})
|
|
200
|
+
</script>
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
const BASE = '/api'
|
|
2
|
+
|
|
3
|
+
async function request(method, path, body = null) {
|
|
4
|
+
const opts = {
|
|
5
|
+
method,
|
|
6
|
+
headers: { 'Content-Type': 'application/json' },
|
|
7
|
+
}
|
|
8
|
+
if (body) opts.body = JSON.stringify(body)
|
|
9
|
+
const res = await fetch(`${BASE}${path}`, opts)
|
|
10
|
+
if (!res.ok) {
|
|
11
|
+
const err = await res.json().catch(() => ({ error: res.statusText }))
|
|
12
|
+
throw new Error(err.error || `HTTP ${res.status}`)
|
|
13
|
+
}
|
|
14
|
+
return res.json()
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const api = {
|
|
18
|
+
config: {
|
|
19
|
+
get: () => request('GET', '/config'),
|
|
20
|
+
update: (data) => request('PUT', '/config', data),
|
|
21
|
+
oauthUrl: () => request('GET', '/config/oauth-url'),
|
|
22
|
+
verifyToken: (token) => request('POST', '/config/verify-token', { token }),
|
|
23
|
+
},
|
|
24
|
+
cfBypass: {
|
|
25
|
+
detect: () => request('POST', '/cf-bypass/detect'),
|
|
26
|
+
save: (data) => request('POST', '/cf-bypass/save', data),
|
|
27
|
+
status: () => request('GET', '/cf-bypass/status'),
|
|
28
|
+
clear: () => request('DELETE', '/cf-bypass'),
|
|
29
|
+
},
|
|
30
|
+
anilist: {
|
|
31
|
+
list: (statuses) => request('GET', `/anilist/list${statuses ? '?statuses=' + statuses : ''}`),
|
|
32
|
+
search: (q) => request('GET', `/anilist/search?q=${encodeURIComponent(q)}`),
|
|
33
|
+
},
|
|
34
|
+
anime: {
|
|
35
|
+
list: () => request('GET', '/anime'),
|
|
36
|
+
get: (id) => request('GET', `/anime/${id}`),
|
|
37
|
+
episodes: (id) => request('GET', `/anime/${id}/episodes`),
|
|
38
|
+
add: (data) => request('POST', '/anime', data),
|
|
39
|
+
remove: (id) => request('DELETE', `/anime/${id}`),
|
|
40
|
+
setEnabled: (id, enabled) => request('PUT', `/anime/${id}/enable`, { enabled }),
|
|
41
|
+
setQuality: (id, quality) => request('PUT', `/anime/${id}/quality`, { quality }),
|
|
42
|
+
setLanguage: (id, language) => request('PUT', `/anime/${id}/language`, { language }),
|
|
43
|
+
},
|
|
44
|
+
match: {
|
|
45
|
+
search: (q, anilistId) => request('GET', `/match/search?q=${encodeURIComponent(q)}${anilistId ? '&anilistId=' + anilistId : ''}`),
|
|
46
|
+
confirm: (data) => request('POST', '/match/confirm', data),
|
|
47
|
+
},
|
|
48
|
+
download: {
|
|
49
|
+
run: (opts) => request('POST', '/download/run', opts),
|
|
50
|
+
anime: (id, opts) => request('POST', `/download/anime/${id}`, opts),
|
|
51
|
+
retry: () => request('POST', '/download/retry'),
|
|
52
|
+
},
|
|
53
|
+
runs: {
|
|
54
|
+
list: (limit) => request('GET', `/runs${limit ? '?limit=' + limit : ''}`),
|
|
55
|
+
},
|
|
56
|
+
daemon: {
|
|
57
|
+
status: () => request('GET', '/daemon/status'),
|
|
58
|
+
start: () => request('POST', '/daemon/start'),
|
|
59
|
+
stop: () => request('POST', '/daemon/stop'),
|
|
60
|
+
restart: () => request('POST', '/daemon/restart'),
|
|
61
|
+
enable: () => request('POST', '/daemon/enable'),
|
|
62
|
+
disable: () => request('POST', '/daemon/disable'),
|
|
63
|
+
},
|
|
64
|
+
update: {
|
|
65
|
+
check: () => request('GET', '/update/check'),
|
|
66
|
+
install: () => request('POST', '/update/install'),
|
|
67
|
+
},
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function createWebSocket() {
|
|
71
|
+
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
|
|
72
|
+
const host = window.location.host
|
|
73
|
+
const ws = new WebSocket(`${protocol}//${host}/ws`)
|
|
74
|
+
return ws
|
|
75
|
+
}
|