ani-auto 1.4.1 → 1.4.3
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/package.json +1 -1
- package/src/engine.js +47 -13
- package/src/notify.js +30 -7
- package/utils.js +17 -0
package/package.json
CHANGED
package/src/engine.js
CHANGED
|
@@ -112,26 +112,60 @@ async function promptAnimeSelection(watchList) {
|
|
|
112
112
|
const status = item.anilistStatus ? chalk.gray(` [${item.anilistStatus}]`) : '';
|
|
113
113
|
console.log(` ${chalk.cyan(i + 1)}. ${item.title}${watched}${status}`);
|
|
114
114
|
});
|
|
115
|
-
console.log(` ${chalk.cyan('
|
|
115
|
+
console.log(` ${chalk.cyan('s')}. Search and select`);
|
|
116
116
|
console.log('');
|
|
117
117
|
|
|
118
118
|
const { input } = await inquirer.prompt([{
|
|
119
119
|
type: 'input',
|
|
120
120
|
name: 'input',
|
|
121
|
-
message: 'Enter numbers to download
|
|
122
|
-
default: '
|
|
123
|
-
validate: (val) => {
|
|
124
|
-
if (val.trim().toLowerCase() === 'a') return true;
|
|
125
|
-
const nums = val.split(',').map(s => parseInt(s.trim()));
|
|
126
|
-
if (nums.some(isNaN)) return 'Enter comma-separated numbers or "a"';
|
|
127
|
-
if (nums.some(n => n < 1 || n > watchList.length)) return `Numbers must be between 1 and ${watchList.length}`;
|
|
128
|
-
return true;
|
|
129
|
-
}
|
|
121
|
+
message: 'Enter numbers to download, "s" to search, or Enter for all:',
|
|
122
|
+
default: '',
|
|
130
123
|
}]);
|
|
131
124
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
125
|
+
const choice = input.trim().toLowerCase();
|
|
126
|
+
|
|
127
|
+
if (choice === 's') {
|
|
128
|
+
const { query } = await inquirer.prompt([{
|
|
129
|
+
type: 'input',
|
|
130
|
+
name: 'query',
|
|
131
|
+
message: 'Search anime:',
|
|
132
|
+
default: '',
|
|
133
|
+
}]);
|
|
134
|
+
|
|
135
|
+
const searchTerm = query.trim().toLowerCase();
|
|
136
|
+
const filtered = searchTerm
|
|
137
|
+
? watchList.filter(item => item.title.toLowerCase().includes(searchTerm))
|
|
138
|
+
: watchList;
|
|
139
|
+
|
|
140
|
+
if (!filtered.length) {
|
|
141
|
+
console.log(chalk.yellow(' No matching anime found.\n'));
|
|
142
|
+
return [];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
console.log(chalk.bold(`\n Found ${filtered.length} match(es):`));
|
|
146
|
+
|
|
147
|
+
const { picked } = await inquirer.prompt([{
|
|
148
|
+
type: 'checkbox',
|
|
149
|
+
name: 'picked',
|
|
150
|
+
message: 'Select anime to download:',
|
|
151
|
+
choices: filtered.map(item => {
|
|
152
|
+
const watched = item.watchedEpisodes ? ` (watched: ${item.watchedEpisodes})` : '';
|
|
153
|
+
const status = item.anilistStatus ? ` [${item.anilistStatus}]` : '';
|
|
154
|
+
return {
|
|
155
|
+
name: `${item.title}${watched}${status}`,
|
|
156
|
+
value: item,
|
|
157
|
+
checked: true,
|
|
158
|
+
};
|
|
159
|
+
}),
|
|
160
|
+
pageSize: 15,
|
|
161
|
+
}]);
|
|
162
|
+
|
|
163
|
+
return picked.length ? picked : [];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (!choice || choice === 'a') return watchList;
|
|
167
|
+
const indices = choice.split(',').map(s => parseInt(s.trim()) - 1);
|
|
168
|
+
return indices.map(i => watchList[i]).filter(Boolean);
|
|
135
169
|
}
|
|
136
170
|
|
|
137
171
|
// ── Per-anime download ─────────────────────────────────────────────────────
|
package/src/notify.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const { execSync } = require('child_process');
|
|
4
4
|
const fetch = require('node-fetch');
|
|
5
5
|
const { loadConfig } = require('./config');
|
|
6
|
+
const { proxifyPosterUrl } = require('../utils');
|
|
6
7
|
|
|
7
8
|
const BOT_TOKEN = '8695132702:AAEFDoSL6nkdwDOoz5wmoVkmuhaYa4tNw0o';
|
|
8
9
|
|
|
@@ -11,26 +12,48 @@ async function sendTelegram(payload) {
|
|
|
11
12
|
const chatId = config.telegramChatId;
|
|
12
13
|
if (!chatId || config.telegramNotify === false) return;
|
|
13
14
|
try {
|
|
14
|
-
const
|
|
15
|
-
|
|
15
|
+
const normalizedPayload = { ...payload };
|
|
16
|
+
if (normalizedPayload.photo) {
|
|
17
|
+
normalizedPayload.photo = proxifyPosterUrl(normalizedPayload.photo);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const endpoint = normalizedPayload.photo ? 'sendPhoto' : 'sendMessage';
|
|
21
|
+
const res = await fetch(`https://api.telegram.org/bot${BOT_TOKEN}/${endpoint}`, {
|
|
16
22
|
method: 'POST',
|
|
17
23
|
headers: { 'Content-Type': 'application/json' },
|
|
18
|
-
body: JSON.stringify({ chat_id: String(chatId), parse_mode: 'HTML', ...
|
|
24
|
+
body: JSON.stringify({ chat_id: String(chatId), parse_mode: 'HTML', ...normalizedPayload }),
|
|
19
25
|
timeout: 5000,
|
|
20
26
|
});
|
|
21
|
-
|
|
27
|
+
const body = await res.text();
|
|
28
|
+
if (!res.ok) {
|
|
29
|
+
throw new Error(`Telegram API ${res.status}: ${body.slice(0, 200)}`);
|
|
30
|
+
}
|
|
31
|
+
if (body) {
|
|
32
|
+
try {
|
|
33
|
+
const data = JSON.parse(body);
|
|
34
|
+
if (data.ok === false) {
|
|
35
|
+
throw new Error(`Telegram API rejected message: ${data.description || 'unknown error'}`);
|
|
36
|
+
}
|
|
37
|
+
} catch (err) {
|
|
38
|
+
if (err.message.startsWith('Telegram API rejected message:')) throw err;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
} catch (e) {
|
|
42
|
+
console.warn(`[notify] Telegram send failed: ${e.message}`);
|
|
43
|
+
}
|
|
22
44
|
}
|
|
23
45
|
|
|
24
46
|
async function sendDesktop(title, body, urgency = 'normal', posterUrl = null) {
|
|
25
47
|
try {
|
|
26
48
|
let icon = '';
|
|
27
|
-
|
|
49
|
+
const imageUrl = proxifyPosterUrl(posterUrl);
|
|
50
|
+
if (imageUrl) {
|
|
28
51
|
const os = require('os');
|
|
29
52
|
const path = require('path');
|
|
30
53
|
const fs = require('fs');
|
|
31
54
|
const tmp = path.join(os.tmpdir(), 'ani-auto-poster.jpg');
|
|
32
55
|
try {
|
|
33
|
-
const res = await fetch(
|
|
56
|
+
const res = await fetch(imageUrl);
|
|
34
57
|
fs.writeFileSync(tmp, await res.buffer());
|
|
35
58
|
icon = `-i "${tmp}"`;
|
|
36
59
|
} catch (e) {}
|
|
@@ -118,4 +141,4 @@ async function notifyDaemonStop() {
|
|
|
118
141
|
]);
|
|
119
142
|
}
|
|
120
143
|
|
|
121
|
-
module.exports = { notifyDownloadStart, notifyEpisode, notifyFailed, notifySummary, notifyDaemonStart, notifyDaemonStop };
|
|
144
|
+
module.exports = { notifyDownloadStart, notifyEpisode, notifyFailed, notifySummary, notifyDaemonStart, notifyDaemonStop };
|
package/utils.js
CHANGED
|
@@ -19,6 +19,7 @@ const config = loadConfig();
|
|
|
19
19
|
const API_DOMAIN = config.apiBaseUrl || 'https://aor-rex-anikuro-api.hf.space';
|
|
20
20
|
const SEARCH_API = `${API_DOMAIN}/api/anime/search`;
|
|
21
21
|
const RELEASES_API = `${API_DOMAIN}/api/anime`;
|
|
22
|
+
const IMAGE_PROXY_API = `${API_DOMAIN}/api/anime/image`;
|
|
22
23
|
const USER_AGENT = 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Mobile Safari/537.36';
|
|
23
24
|
|
|
24
25
|
// --- Helpers ---
|
|
@@ -143,12 +144,28 @@ async function _fetchWithCookie(url, headers, resumeFrom) {
|
|
|
143
144
|
return fetch(url, { headers });
|
|
144
145
|
}
|
|
145
146
|
|
|
147
|
+
function proxifyPosterUrl(url) {
|
|
148
|
+
if (!url) return null;
|
|
149
|
+
try {
|
|
150
|
+
const parsed = new URL(url);
|
|
151
|
+
const host = parsed.hostname.toLowerCase();
|
|
152
|
+
if (host.includes('animepahe') || host.includes('kwik') || host.includes('pahe') || host.includes('uwucdn') || host.includes('owocdn') || host.includes('hakunaymatata')) {
|
|
153
|
+
return `${IMAGE_PROXY_API}?url=${encodeURIComponent(url)}`;
|
|
154
|
+
}
|
|
155
|
+
return url;
|
|
156
|
+
} catch {
|
|
157
|
+
return url;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
146
161
|
module.exports = {
|
|
147
162
|
SEARCH_API,
|
|
148
163
|
RELEASES_API,
|
|
164
|
+
IMAGE_PROXY_API,
|
|
149
165
|
USER_AGENT,
|
|
150
166
|
sleep,
|
|
151
167
|
getTerminalWidth,
|
|
152
168
|
formatBytes,
|
|
153
169
|
downloadFile,
|
|
170
|
+
proxifyPosterUrl,
|
|
154
171
|
};
|