ani-auto 1.4.3 → 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.
Files changed (90) hide show
  1. package/anime/LICENSE +21 -0
  2. package/anime/api/index.js +110 -0
  3. package/anime/app.js +79 -0
  4. package/anime/controllers/animeInfoController.js +48 -0
  5. package/anime/controllers/animeListController.js +21 -0
  6. package/anime/controllers/homeController.js +42 -0
  7. package/anime/controllers/playController.js +140 -0
  8. package/anime/controllers/queueController.js +20 -0
  9. package/anime/controllers/testController.js +667 -0
  10. package/anime/index.js +139 -0
  11. package/anime/middleware/cache.js +64 -0
  12. package/anime/middleware/errorHandler.js +33 -0
  13. package/anime/middleware/rateLimiter.js +73 -0
  14. package/anime/models/animeInfoModel.js +193 -0
  15. package/anime/models/animeListModel.js +69 -0
  16. package/anime/models/homeModel.js +33 -0
  17. package/anime/models/playModel.js +528 -0
  18. package/anime/models/queueModel.js +22 -0
  19. package/anime/package.json +27 -0
  20. package/anime/pnpm-lock.yaml +1774 -0
  21. package/anime/readme.md +236 -0
  22. package/anime/routes/animeInfoRoutes.js +9 -0
  23. package/anime/routes/animeListRoutes.js +9 -0
  24. package/anime/routes/homeRoutes.js +10 -0
  25. package/anime/routes/playRoutes.js +11 -0
  26. package/anime/routes/queueRoutes.js +8 -0
  27. package/anime/routes/testRoutes.js +325 -0
  28. package/anime/routes/webhookRoutes.js +23 -0
  29. package/anime/scrapers/animepahe.js +1053 -0
  30. package/anime/test-server.js +10 -0
  31. package/anime/test.sh +275 -0
  32. package/anime/utils/browser.js +172 -0
  33. package/anime/utils/config.js +209 -0
  34. package/anime/utils/dataProcessor.js +172 -0
  35. package/anime/utils/diskCache.js +228 -0
  36. package/anime/utils/flaresolverr.js +361 -0
  37. package/anime/utils/jsParser.js +19 -0
  38. package/anime/utils/redis.js +64 -0
  39. package/anime/utils/requestManager.js +706 -0
  40. package/anime/utils/urlConverter.js +88 -0
  41. package/anime/utils/webhookNotifier.js +190 -0
  42. package/cookiereader.py +291 -0
  43. package/package.json +14 -6
  44. package/src/anilist.js +19 -2
  45. package/src/api/anilist.js +32 -0
  46. package/src/api/anime.js +181 -0
  47. package/src/api/cf-bypass.js +131 -0
  48. package/src/api/config.js +134 -0
  49. package/src/api/daemon.js +62 -0
  50. package/src/api/download.js +98 -0
  51. package/src/api/match.js +58 -0
  52. package/src/api/runs.js +15 -0
  53. package/src/api/update.js +96 -0
  54. package/src/cli.js +18 -2
  55. package/src/config.js +14 -2
  56. package/src/daemon.js +1 -1
  57. package/src/db.js +38 -11
  58. package/src/engine.js +66 -13
  59. package/src/notify.js +82 -7
  60. package/src/scraper.js +2 -2
  61. package/src/server.js +108 -0
  62. package/utils.js +21 -21
  63. package/web/index.html +13 -0
  64. package/web/package-lock.json +1420 -0
  65. package/web/package.json +24 -0
  66. package/web/src/App.vue +200 -0
  67. package/web/src/api/client.js +75 -0
  68. package/web/src/assets/styles/base.css +961 -0
  69. package/web/src/components/UpdateModal.vue +157 -0
  70. package/web/src/components/sidebar/SidebarResizable.vue +170 -0
  71. package/web/src/components/sidebar/sidebarConfig.js +24 -0
  72. package/web/src/main.js +104 -0
  73. package/web/src/router/index.js +44 -0
  74. package/web/src/stores/config.js +27 -0
  75. package/web/src/stores/download.js +107 -0
  76. package/web/src/stores/update.js +73 -0
  77. package/web/src/views/About.vue +42 -0
  78. package/web/src/views/AniListSync.vue +122 -0
  79. package/web/src/views/AnimeDetail.vue +202 -0
  80. package/web/src/views/AnimeList.vue +110 -0
  81. package/web/src/views/CFResult.vue +312 -0
  82. package/web/src/views/DaemonControl.vue +83 -0
  83. package/web/src/views/Dashboard.vue +187 -0
  84. package/web/src/views/DownloadCenter.vue +153 -0
  85. package/web/src/views/MatchFinder.vue +131 -0
  86. package/web/src/views/OAuthCallback.vue +44 -0
  87. package/web/src/views/Onboarding.vue +93 -0
  88. package/web/src/views/RunHistory.vue +122 -0
  89. package/web/src/views/Settings.vue +410 -0
  90. package/web/vite.config.js +23 -0
@@ -0,0 +1,410 @@
1
+ <template>
2
+ <div>
3
+ <div class="page-header">
4
+ <h2>Settings</h2>
5
+ </div>
6
+
7
+ <Skeleton v-if="loading" width="100%" height="400px" />
8
+
9
+ <Tabs v-else-if="config" v-model:value="activeTab">
10
+ <TabList>
11
+ <Tab value="0">AniList</Tab>
12
+ <Tab value="1">Download</Tab>
13
+ <Tab value="2">Daemon</Tab>
14
+ <Tab value="3">Notifications</Tab>
15
+ <Tab value="4">Updates</Tab>
16
+ </TabList>
17
+ <TabPanels>
18
+ <TabPanel value="0">
19
+ <Card>
20
+ <template #content>
21
+ <div class="form-group">
22
+ <label>Username</label>
23
+ <InputText v-model="form.anilistUsername" placeholder="AniList username" fluid />
24
+ </div>
25
+ <div class="form-group">
26
+ <label>Token <Badge v-if="config.anilistToken === '[set]'" value="set" severity="success" size="small" style="margin-left:6px" /></label>
27
+ <div class="form-field-row">
28
+ <InputText v-model="form.anilistToken" :placeholder="config.anilistToken === '[set]' ? 'Token is set' : 'Paste token'" type="password" fluid style="flex:1" />
29
+ <Button label="Verify" @click="verifyToken" :disabled="!form.anilistToken" />
30
+ </div>
31
+ </div>
32
+ <div v-if="!config.anilistUsername" class="form-group" style="margin-top:16px">
33
+ <p style="margin-bottom:8px;color:var(--ink-400);font-size:13px">Or sign in with AniList:</p>
34
+ <Button label="Connect AniList" severity="primary" @click="connectAnilist" />
35
+ </div>
36
+ <div v-else class="form-group" style="margin-top:16px">
37
+ <Button label="Disconnect AniList" severity="danger" @click="disconnectAnilist" />
38
+ </div>
39
+ <div class="form-group">
40
+ <label>Watch Statuses</label>
41
+ <div class="checkbox-group">
42
+ <div v-for="s in statuses" :key="s" class="checkbox-item">
43
+ <Checkbox v-model="form.watchStatuses" :value="s" :inputId="'status-' + s" />
44
+ <label :for="'status-' + s">{{ s }}</label>
45
+ </div>
46
+ </div>
47
+ </div>
48
+ <div class="form-group" style="margin-top:16px;padding-top:16px;border-top:1px solid var(--border-color)">
49
+ <label>AnimePahe Access</label>
50
+ <p style="color:var(--ink-400);font-size:12px;margin-bottom:8px">
51
+ Cloudflare bypass status: <strong>{{ cfStatus }}</strong>
52
+ </p>
53
+
54
+ <div class="form-field-row" style="margin-bottom:8px">
55
+ <Button
56
+ label="Bypass Cloudflare"
57
+ severity="secondary"
58
+ @click="startCfBypass"
59
+ />
60
+ <Button
61
+ v-if="config.animepaheCfClearance === '[set]'"
62
+ label="Clear Cookies"
63
+ severity="danger"
64
+ @click="clearCfBypass"
65
+ />
66
+ </div>
67
+
68
+ <div class="form-group" style="margin-top:8px">
69
+ <label for="cf-input">cf_clearance Value</label>
70
+ <Textarea
71
+ id="cf-input"
72
+ v-model="cfInput"
73
+ :placeholder="config.animepaheCfClearance === '[set]' ? 'cf_clearance is set — paste new value to replace' : 'Paste cf_clearance value here'"
74
+ rows="2"
75
+ fluid
76
+ />
77
+ </div>
78
+
79
+ <div class="form-group" style="margin-top:8px">
80
+ <label for="ua-input">User-Agent</label>
81
+ <InputText
82
+ id="ua-input"
83
+ v-model="uaInput"
84
+ placeholder="Mozilla/5.0 (X11; Linux x86_64; rv:151.0) Gecko/20100101 Firefox/151.0"
85
+ fluid
86
+ />
87
+ </div>
88
+
89
+ <div class="form-field-row">
90
+ <Button
91
+ label="Save & Verify"
92
+ severity="primary"
93
+ @click="saveAndVerify"
94
+ :disabled="!cfInput"
95
+ :loading="verifying"
96
+ />
97
+ <span v-if="verifyResult" :class="verifyResult.ok ? 'text-green-600' : 'text-red-500'" style="font-size:13px;margin-left:8px">
98
+ {{ verifyResult.message }}
99
+ </span>
100
+ </div>
101
+ </div>
102
+ </template>
103
+ </Card>
104
+ </TabPanel>
105
+ <TabPanel value="1">
106
+ <Card>
107
+ <template #content>
108
+ <div class="form-row">
109
+ <div class="form-group">
110
+ <label>Quality</label>
111
+ <Select v-model="form.quality" :options="['1080p', '720p', '360p']" fluid />
112
+ </div>
113
+ <div class="form-group">
114
+ <label>Language</label>
115
+ <Select v-model="form.language" :options="['sub', 'dub']" fluid />
116
+ </div>
117
+ </div>
118
+ <div class="form-row">
119
+ <div class="form-group">
120
+ <label>Concurrency</label>
121
+ <InputNumber v-model="form.concurrency" :min="1" :max="10" fluid />
122
+ </div>
123
+ <div class="form-group">
124
+ <label>Max Retries</label>
125
+ <InputNumber v-model="form.maxRetries" :min="0" :max="10" fluid />
126
+ </div>
127
+ </div>
128
+ <div class="form-group">
129
+ <label>Output Directory</label>
130
+ <InputText v-model="form.outputDir" fluid />
131
+ </div>
132
+ </template>
133
+ </Card>
134
+ </TabPanel>
135
+ <TabPanel value="2">
136
+ <Card>
137
+ <template #content>
138
+ <div class="form-row">
139
+ <div class="form-group">
140
+ <label>Check Interval (minutes)</label>
141
+ <InputNumber v-model="form.daemonIntervalMinutes" :min="5" :max="1440" fluid />
142
+ </div>
143
+ <div class="form-group">
144
+ <label>Airing Buffer (minutes)</label>
145
+ <InputNumber v-model="form.airingBufferMinutes" fluid />
146
+ </div>
147
+ </div>
148
+ </template>
149
+ </Card>
150
+ </TabPanel>
151
+ <TabPanel value="3">
152
+ <Card>
153
+ <template #content>
154
+ <div class="form-group">
155
+ <label>Telegram Chat ID</label>
156
+ <InputText v-model="form.telegramChatId" fluid />
157
+ </div>
158
+ <div class="form-group">
159
+ <div class="checkbox-item">
160
+ <ToggleSwitch v-model="form.telegramNotify" :inputId="'tg-notify'" />
161
+ <label :for="'tg-notify'" style="margin-left:8px">Enable Telegram notifications</label>
162
+ </div>
163
+ </div>
164
+ </template>
165
+ </Card>
166
+ </TabPanel>
167
+ <TabPanel value="4">
168
+ <Card>
169
+ <template #content>
170
+ <div class="form-group">
171
+ <div class="checkbox-item">
172
+ <ToggleSwitch v-model="form.autoUpdateCheck" :inputId="'auto-update'" />
173
+ <label :for="'auto-update'" style="margin-left:8px">Auto-check for updates on app start</label>
174
+ </div>
175
+ <p class="form-hint">When enabled, ani-auto will check for new versions when you open the web UI.</p>
176
+ </div>
177
+ <div class="form-group">
178
+ <label>Current Version</label>
179
+ <span class="version-display">v1.4.4</span>
180
+ </div>
181
+ <div class="form-group">
182
+ <Button
183
+ label="Check for Updates"
184
+ @click="checkUpdate"
185
+ :loading="updateStore.checking"
186
+ :disabled="updateStore.installing"
187
+ />
188
+ </div>
189
+ <div v-if="updateStore.updateAvailable" class="update-result">
190
+ <span class="update-result-text">Update v{{ updateStore.latestVersion }} available</span>
191
+ <Button
192
+ label="Install Update"
193
+ @click="installUpdate"
194
+ :loading="updateStore.installing"
195
+ />
196
+ </div>
197
+ <div v-if="updateStore.currentVersion && !updateStore.updateAvailable && !updateStore.checking" class="update-result">
198
+ <span class="update-result-text update-result-ok">Already on latest version (v{{ updateStore.currentVersion }})</span>
199
+ </div>
200
+ </template>
201
+ </Card>
202
+ </TabPanel>
203
+ </TabPanels>
204
+ </Tabs>
205
+
206
+ <div v-if="config" class="settings-actions">
207
+ <Button label="Save Settings" @click="save" />
208
+ <span v-if="saved" class="saved-msg">Saved</span>
209
+ </div>
210
+ </div>
211
+ </template>
212
+
213
+ <script setup>
214
+ import { ref, reactive, computed, onMounted, onBeforeUnmount } from 'vue'
215
+ import { useRouter, useRoute } from 'vue-router'
216
+ import { api } from '../api/client'
217
+ import { useConfigStore } from '../stores/config'
218
+ import Tabs from 'primevue/tabs'
219
+ import TabList from 'primevue/tablist'
220
+ import Tab from 'primevue/tab'
221
+ import TabPanels from 'primevue/tabpanels'
222
+ import TabPanel from 'primevue/tabpanel'
223
+
224
+ import { useUpdateStore } from '../stores/update'
225
+
226
+ const router = useRouter()
227
+ const route = useRoute()
228
+ const configStore = useConfigStore()
229
+ const updateStore = useUpdateStore()
230
+ const loading = ref(true)
231
+ const config = ref(null)
232
+ const saved = ref(false)
233
+ const activeTab = ref('0')
234
+ const closeFirefoxWarning = ref(false)
235
+ const cfInput = ref('')
236
+ const uaInput = ref('')
237
+ const verifying = ref(false)
238
+ const verifyResult = ref(null)
239
+
240
+ const cfStatus = computed(() => config.value?.animepaheCfClearance === '[set]' ? 'Active' : 'Not bypassed')
241
+
242
+ const statuses = ['CURRENT', 'PLANNING', 'PAUSED', 'REPEATING']
243
+
244
+ const form = reactive({
245
+ anilistUsername: '',
246
+ anilistToken: '',
247
+ watchStatuses: ['CURRENT'],
248
+ quality: '1080p',
249
+ language: 'sub',
250
+ concurrency: 2,
251
+ maxRetries: 3,
252
+ outputDir: '',
253
+ daemonIntervalMinutes: 120,
254
+ airingBufferMinutes: 5,
255
+ telegramChatId: '',
256
+ telegramNotify: true,
257
+ autoUpdateCheck: true,
258
+ })
259
+
260
+ onMounted(async () => {
261
+ try {
262
+ config.value = await api.config.get()
263
+ Object.assign(form, config.value)
264
+ if (form.anilistToken === '[set]') form.anilistToken = ''
265
+ delete form.animepaheCfClearance
266
+ delete form.animepaheUserAgent
267
+
268
+ const state = history.state
269
+ if (state?.cfClearance) {
270
+ cfInput.value = state.cfClearance
271
+ }
272
+ if (state?.userAgent) {
273
+ uaInput.value = state.userAgent
274
+ }
275
+ history.replaceState(null, '')
276
+ } catch (e) {
277
+ console.error(e)
278
+ } finally {
279
+ loading.value = false
280
+ }
281
+ const handler = async (e) => {
282
+ if (e.data?.type === 'anilist-token') {
283
+ const updated = await api.config.get()
284
+ config.value = updated
285
+ Object.assign(form, updated)
286
+ if (form.anilistToken === '[set]') form.anilistToken = ''
287
+ delete form.animepaheCfClearance
288
+ delete form.animepaheUserAgent
289
+ }
290
+ }
291
+ window.addEventListener('message', handler)
292
+ onBeforeUnmount(() => window.removeEventListener('message', handler))
293
+ })
294
+
295
+ async function verifyToken() {
296
+ if (!form.anilistToken) return
297
+ try {
298
+ const res = await api.config.verifyToken(form.anilistToken)
299
+ if (res.ok) {
300
+ await api.config.update({
301
+ anilistToken: form.anilistToken,
302
+ anilistUsername: res.name,
303
+ anilistAvatar: res.avatar,
304
+ })
305
+ form.anilistUsername = res.name
306
+ form.anilistAvatar = res.avatar
307
+ form.anilistToken = ''
308
+ await configStore.fetch()
309
+ config.value = configStore.config
310
+ Object.assign(form, config.value)
311
+ if (form.anilistToken === '[set]') form.anilistToken = ''
312
+ } else {
313
+ alert(`Verification failed: ${res.error}`)
314
+ }
315
+ } catch (e) {
316
+ alert(`Error: ${e.message}`)
317
+ }
318
+ }
319
+
320
+ async function connectAnilist() {
321
+ const { url } = await api.config.oauthUrl()
322
+ window.open(url, 'anilist-oauth', 'width=600,height=700')
323
+ }
324
+
325
+ async function disconnectAnilist() {
326
+ await api.config.update({
327
+ anilistToken: null,
328
+ anilistUsername: null,
329
+ anilistAvatar: null,
330
+ onboardingSkipped: true,
331
+ })
332
+ configStore.config = null
333
+ await configStore.fetch()
334
+ if (configStore.config) {
335
+ config.value = configStore.config
336
+ Object.assign(form, config.value)
337
+ if (form.anilistToken === '[set]') form.anilistToken = ''
338
+ }
339
+ router.push('/settings')
340
+ }
341
+
342
+ async function startCfBypass() {
343
+ router.push({ name: 'cf-result' })
344
+ }
345
+
346
+ async function clearCfBypass() {
347
+ try {
348
+ closeFirefoxWarning.value = false
349
+ verifyResult.value = null
350
+ const res = await api.cfBypass.clear()
351
+ config.value = await api.config.get()
352
+ Object.assign(form, config.value)
353
+ if (form.anilistToken === '[set]') form.anilistToken = ''
354
+ cfInput.value = ''
355
+ uaInput.value = ''
356
+ if (res.locked?.length > 0) {
357
+ closeFirefoxWarning.value = true
358
+ }
359
+ } catch (e) {
360
+ alert(`Clear failed: ${e.message}`)
361
+ }
362
+ }
363
+
364
+ async function saveAndVerify() {
365
+ if (!cfInput.value) return
366
+ verifying.value = true
367
+ verifyResult.value = null
368
+ try {
369
+ const res = await api.cfBypass.save({ cfClearance: cfInput.value, userAgent: uaInput.value })
370
+ if (res.ok) {
371
+ verifyResult.value = { ok: true, message: 'Saved and verified!' }
372
+ config.value = await api.config.get()
373
+ Object.assign(form, config.value)
374
+ if (form.anilistToken === '[set]') form.anilistToken = ''
375
+ delete form.animepaheCfClearance
376
+ delete form.animepaheUserAgent
377
+ } else {
378
+ verifyResult.value = { ok: false, message: res.error || 'Verification failed' }
379
+ }
380
+ } catch (e) {
381
+ verifyResult.value = { ok: false, message: e.message }
382
+ } finally {
383
+ verifying.value = false
384
+ }
385
+ }
386
+
387
+ async function save() {
388
+ saved.value = false
389
+ try {
390
+ const payload = { ...form }
391
+ if (!payload.anilistToken && config.value?.anilistToken === '[set]') {
392
+ delete payload.anilistToken
393
+ }
394
+ await api.config.update(payload)
395
+ await configStore.fetch()
396
+ saved.value = true
397
+ setTimeout(() => saved.value = false, 2000)
398
+ } catch (e) {
399
+ alert(`Save failed: ${e.message}`)
400
+ }
401
+ }
402
+
403
+ async function checkUpdate() {
404
+ await updateStore.check()
405
+ }
406
+
407
+ function installUpdate() {
408
+ updateStore.install()
409
+ }
410
+ </script>
@@ -0,0 +1,23 @@
1
+ import { defineConfig } from 'vite'
2
+ import vue from '@vitejs/plugin-vue'
3
+
4
+ export default defineConfig({
5
+ plugins: [vue()],
6
+ server: {
7
+ port: 2641,
8
+ proxy: {
9
+ '/api': {
10
+ target: 'http://localhost:2640',
11
+ changeOrigin: true,
12
+ },
13
+ '/ws': {
14
+ target: 'ws://localhost:2640',
15
+ ws: true,
16
+ },
17
+ },
18
+ },
19
+ build: {
20
+ outDir: 'dist',
21
+ emptyOutDir: true,
22
+ },
23
+ })