weebcli 1.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/.github/workflows/releases.yml +39 -0
- package/LICENSE +400 -0
- package/README-EN.md +134 -0
- package/README.md +134 -0
- package/aur/.SRCINFO +16 -0
- package/aur/PKGBUILD +43 -0
- package/eslint.config.js +49 -0
- package/jsconfig.json +9 -0
- package/package.json +45 -0
- package/src/constants.js +13 -0
- package/src/functions/episodes.js +38 -0
- package/src/functions/time.js +27 -0
- package/src/functions/variables.js +3 -0
- package/src/i18n/en.json +351 -0
- package/src/i18n/index.js +80 -0
- package/src/i18n/tr.json +348 -0
- package/src/index.js +307 -0
- package/src/jsdoc.js +72 -0
- package/src/sources/allanime.js +195 -0
- package/src/sources/animecix.js +223 -0
- package/src/sources/animely.js +100 -0
- package/src/sources/handlers/allanime.js +318 -0
- package/src/sources/handlers/animecix.js +316 -0
- package/src/sources/handlers/animely.js +338 -0
- package/src/sources/handlers/base.js +391 -0
- package/src/sources/handlers/index.js +4 -0
- package/src/sources/index.js +80 -0
- package/src/utils/anilist.js +193 -0
- package/src/utils/data_manager.js +27 -0
- package/src/utils/discord.js +86 -0
- package/src/utils/download/concurrency.js +27 -0
- package/src/utils/download/download.js +485 -0
- package/src/utils/download/progress.js +84 -0
- package/src/utils/players/mpv.js +251 -0
- package/src/utils/players/vlc.js +120 -0
- package/src/utils/process_queue.js +121 -0
- package/src/utils/resume_watch.js +137 -0
- package/src/utils/search.js +39 -0
- package/src/utils/search_download.js +21 -0
- package/src/utils/speedtest.js +30 -0
- package/src/utils/spinner.js +7 -0
- package/src/utils/storage/cache.js +42 -0
- package/src/utils/storage/config.js +71 -0
- package/src/utils/storage/history.js +69 -0
- package/src/utils/storage/queue.js +43 -0
- package/src/utils/storage/watch_progress.js +104 -0
- package/src/utils/system.js +176 -0
- package/src/utils/ui/box.js +140 -0
- package/src/utils/ui/settings_ui.js +322 -0
- package/src/utils/ui/show_history.js +92 -0
- package/src/utils/ui/stats.js +67 -0
- package/start.js +21 -0
- package/tanitim-en.md +66 -0
- package/tanitim-tr.md +66 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import inquirer from "inquirer";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import { getConfig, saveConfig } from "../storage/config.js";
|
|
5
|
+
import { successBox } from "./box.js";
|
|
6
|
+
import { authenticate, verifyToken } from "../anilist.js";
|
|
7
|
+
import { spinner } from "../spinner.js";
|
|
8
|
+
import { commandExists, installPackage } from "../system.js";
|
|
9
|
+
import { sources, getSourcesByLanguage } from "../../sources/index.js";
|
|
10
|
+
import { t, setLanguage } from "../../i18n/index.js";
|
|
11
|
+
|
|
12
|
+
const pkg = JSON.parse(fs.readFileSync(new URL("./../../../package.json", import.meta.url), "utf-8"));
|
|
13
|
+
|
|
14
|
+
export async function showSettings() {
|
|
15
|
+
const config = getConfig();
|
|
16
|
+
const currentSource = sources.find(s => s.id === config.defaultSource) || sources[0];
|
|
17
|
+
const currentLang = config.language === "en" ? "English" : "Türkçe";
|
|
18
|
+
|
|
19
|
+
console.clear();
|
|
20
|
+
console.log(chalk.bgCyan.black(` ${t("settings.title")} `) + chalk.gray(` v${pkg.version}`));
|
|
21
|
+
console.log();
|
|
22
|
+
|
|
23
|
+
const { action } = await inquirer.prompt([{
|
|
24
|
+
type: "list",
|
|
25
|
+
name: "action",
|
|
26
|
+
message: t("settings.whatToChange"),
|
|
27
|
+
pageSize: 15,
|
|
28
|
+
loop: false,
|
|
29
|
+
choices: [
|
|
30
|
+
{ name: t("settings.language", { current: chalk.yellow(currentLang) }), value: "language" },
|
|
31
|
+
{ name: t("settings.animeSource", { current: chalk.yellow(currentSource.name) }), value: "defaultSource" },
|
|
32
|
+
{ name: t("settings.player", { current: chalk.yellow(config.defaultPlayer || t("settings.notSelected")) }), value: "defaultPlayer" },
|
|
33
|
+
{ name: t("settings.aria2", { status: chalk.yellow(config.useAria2 ? t("settings.active") : t("settings.inactive")) }), value: "aria2" },
|
|
34
|
+
...(config.useAria2 ? [{ name: t("settings.aria2Connections", { count: chalk.yellow(config.aria2Connections) }), value: "aria2Connections" }] : []),
|
|
35
|
+
{ name: t("settings.ytDlp", { status: chalk.yellow(config.useYtDlp !== false ? t("settings.active") : t("settings.inactive")) }), value: "ytDlp" },
|
|
36
|
+
...(config.useYtDlp !== false ? [{ name: t("settings.ytDlpConnections", { count: chalk.yellow(config.ytDlpConnections || 16) }), value: "ytDlpConnections" }] : []),
|
|
37
|
+
{ name: t("settings.concurrentLimit", { count: chalk.yellow(config.maxConcurrent) }), value: "maxConcurrent" },
|
|
38
|
+
{ name: t("settings.downloadLocation", { path: chalk.yellow(config.downloadDir) }), value: "downloadDir" },
|
|
39
|
+
{ name: t("settings.retryDownload", { status: chalk.yellow(config.retryEnabled ? t("settings.active") : t("settings.inactive")) }), value: "retryToggle" },
|
|
40
|
+
...(config.retryEnabled ? [{ name: t("settings.retrySettings", { count: chalk.yellow(config.retryCount), delay: chalk.yellow(config.retryDelay / 1000) }), value: "retrySettings" }] : []),
|
|
41
|
+
{ name: t("settings.showDetails", { status: chalk.yellow(config.showAnimeDetails !== false ? t("settings.active") : t("settings.inactive")) }), value: "detailsToggle" },
|
|
42
|
+
{ name: t("settings.anilist", { status: chalk.yellow(config.anilistUsername || t("settings.notConnected")) }), value: "anilist" },
|
|
43
|
+
...(config.anilistToken ? [{ name: t("settings.anilistLogout"), value: "anilistLogout" }] : []),
|
|
44
|
+
new inquirer.Separator(),
|
|
45
|
+
{ name: t("settings.backToMenu"), value: "back" }
|
|
46
|
+
]
|
|
47
|
+
}]);
|
|
48
|
+
|
|
49
|
+
if (action === "back") return;
|
|
50
|
+
|
|
51
|
+
if (action === "language") {
|
|
52
|
+
const { lang } = await inquirer.prompt([{
|
|
53
|
+
type: "list",
|
|
54
|
+
name: "lang",
|
|
55
|
+
message: t("settings.selectLanguage"),
|
|
56
|
+
choices: [
|
|
57
|
+
{ name: t("settings.turkish"), value: "tr" },
|
|
58
|
+
{ name: t("settings.english"), value: "en" }
|
|
59
|
+
],
|
|
60
|
+
default: config.language || "tr"
|
|
61
|
+
}]);
|
|
62
|
+
|
|
63
|
+
config.language = lang;
|
|
64
|
+
setLanguage(lang);
|
|
65
|
+
|
|
66
|
+
const availableSources = getSourcesByLanguage(lang);
|
|
67
|
+
const currentSourceValid = availableSources.some(s => s.id === config.defaultSource);
|
|
68
|
+
if (!currentSourceValid && availableSources.length > 0) {
|
|
69
|
+
config.defaultSource = availableSources[0].id;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
saveConfig(config);
|
|
73
|
+
successBox(t("settings.languageUpdated"));
|
|
74
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
75
|
+
await showSettings();
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (action === "defaultSource") {
|
|
80
|
+
const currentLangCode = config.language || "tr";
|
|
81
|
+
const availableSources = getSourcesByLanguage(currentLangCode);
|
|
82
|
+
|
|
83
|
+
const { source } = await inquirer.prompt([{
|
|
84
|
+
type: "list",
|
|
85
|
+
name: "source",
|
|
86
|
+
message: t("settings.selectSource"),
|
|
87
|
+
choices: availableSources.map(s => ({
|
|
88
|
+
name: `${s.name}${s.supportsLocalSearch ? chalk.gray(` (${t("settings.advancedSearch")})`) : ""}`,
|
|
89
|
+
value: s.id
|
|
90
|
+
})),
|
|
91
|
+
default: config.defaultSource || availableSources[0]?.id
|
|
92
|
+
}]);
|
|
93
|
+
|
|
94
|
+
config.defaultSource = source;
|
|
95
|
+
saveConfig(config);
|
|
96
|
+
successBox(t("settings.sourceUpdated"));
|
|
97
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
98
|
+
await showSettings();
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (action === "detailsToggle") {
|
|
103
|
+
config.showAnimeDetails = config.showAnimeDetails === false ? true : false;
|
|
104
|
+
saveConfig(config);
|
|
105
|
+
successBox(config.showAnimeDetails ? t("settings.detailsEnabled") : t("settings.detailsDisabled"));
|
|
106
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
107
|
+
await showSettings();
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
if (action === "anilistLogout") {
|
|
113
|
+
const { confirm } = await inquirer.prompt([{
|
|
114
|
+
type: "confirm",
|
|
115
|
+
name: "confirm",
|
|
116
|
+
message: t("settings.anilistDisconnectConfirm"),
|
|
117
|
+
default: false
|
|
118
|
+
}]);
|
|
119
|
+
|
|
120
|
+
if (confirm) {
|
|
121
|
+
config.anilistToken = undefined;
|
|
122
|
+
config.anilistUsername = undefined;
|
|
123
|
+
saveConfig(config);
|
|
124
|
+
console.log(chalk.yellow("\n" + t("settings.anilistDisconnected")));
|
|
125
|
+
} else {
|
|
126
|
+
console.log(chalk.gray(t("settings.operationCancelled")));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
await new Promise(resolve => setTimeout(resolve, 1500));
|
|
130
|
+
await showSettings();
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (action === "anilist") {
|
|
135
|
+
if (config.anilistToken) {
|
|
136
|
+
await showSettings();
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
const token = await authenticate();
|
|
142
|
+
spinner.start(t("anilist.verifying"));
|
|
143
|
+
const username = await verifyToken(token);
|
|
144
|
+
spinner.stop();
|
|
145
|
+
|
|
146
|
+
if (username) {
|
|
147
|
+
config.anilistToken = token;
|
|
148
|
+
config.anilistUsername = username;
|
|
149
|
+
console.log(chalk.green(`\n${t("anilist.loginSuccess", { username })}`));
|
|
150
|
+
} else {
|
|
151
|
+
console.log(chalk.red("\n" + t("anilist.verifyFailed")));
|
|
152
|
+
}
|
|
153
|
+
} catch (error) {
|
|
154
|
+
console.log(chalk.red("\n" + t("anilist.loginFailed", { message: error.message })));
|
|
155
|
+
}
|
|
156
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
157
|
+
saveConfig(config);
|
|
158
|
+
await showSettings();
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
if (action === "defaultPlayer") {
|
|
164
|
+
const { player } = await inquirer.prompt([{
|
|
165
|
+
type: "list",
|
|
166
|
+
name: "player",
|
|
167
|
+
message: t("settings.selectPlayer"),
|
|
168
|
+
choices: [
|
|
169
|
+
{ name: t("setup.mpvWithResume"), value: "mpv" },
|
|
170
|
+
{ name: t("setup.vlcPlayer"), value: "vlc" }
|
|
171
|
+
],
|
|
172
|
+
default: config.defaultPlayer || "mpv"
|
|
173
|
+
}]);
|
|
174
|
+
|
|
175
|
+
if (!commandExists(player)) {
|
|
176
|
+
console.log(chalk.yellow(`\n${t("settings.playerNotFound", { player })}`));
|
|
177
|
+
const { install } = await inquirer.prompt([{
|
|
178
|
+
type: "confirm",
|
|
179
|
+
name: "install",
|
|
180
|
+
message: t("settings.autoInstallPrompt"),
|
|
181
|
+
default: true
|
|
182
|
+
}]);
|
|
183
|
+
|
|
184
|
+
if (install) {
|
|
185
|
+
console.log(chalk.yellow(t("settings.installingPlayer")));
|
|
186
|
+
const success = installPackage(player);
|
|
187
|
+
if (success) {
|
|
188
|
+
console.log(chalk.green(`\n${t("settings.playerInstalled", { player })}`));
|
|
189
|
+
} else {
|
|
190
|
+
console.log(chalk.red("\n" + t("settings.installFailed")));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
config.defaultPlayer = player;
|
|
196
|
+
saveConfig(config);
|
|
197
|
+
successBox(t("settings.playerUpdated"));
|
|
198
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
199
|
+
await showSettings();
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (action === "maxConcurrent") {
|
|
204
|
+
const { limit } = await inquirer.prompt([{
|
|
205
|
+
type: "number",
|
|
206
|
+
name: "limit",
|
|
207
|
+
message: t("settings.concurrentPrompt"),
|
|
208
|
+
default: config.maxConcurrent,
|
|
209
|
+
validate: (input) => (input > 0 && input <= 10) ? true : t("settings.concurrentValidation")
|
|
210
|
+
}]);
|
|
211
|
+
config.maxConcurrent = limit;
|
|
212
|
+
saveConfig(config);
|
|
213
|
+
successBox(t("settings.concurrentUpdated"));
|
|
214
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
215
|
+
await showSettings();
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (action === "aria2") {
|
|
220
|
+
config.useAria2 = !config.useAria2;
|
|
221
|
+
saveConfig(config);
|
|
222
|
+
successBox(config.useAria2 ? t("settings.aria2Enabled") : t("settings.aria2Disabled"));
|
|
223
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
224
|
+
await showSettings();
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (action === "aria2Connections") {
|
|
229
|
+
const { connections } = await inquirer.prompt([{
|
|
230
|
+
type: "number",
|
|
231
|
+
name: "connections",
|
|
232
|
+
message: t("settings.aria2ConnectionsPrompt"),
|
|
233
|
+
default: config.aria2Connections || 16,
|
|
234
|
+
validate: (input) => (input > 0 && input <= 32) ? true : t("settings.aria2ConnectionsValidation")
|
|
235
|
+
}]);
|
|
236
|
+
config.aria2Connections = connections;
|
|
237
|
+
saveConfig(config);
|
|
238
|
+
successBox(t("settings.aria2ConnectionsUpdated"));
|
|
239
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
240
|
+
await showSettings();
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (action === "ytDlp") {
|
|
245
|
+
config.useYtDlp = config.useYtDlp === false ? true : false;
|
|
246
|
+
saveConfig(config);
|
|
247
|
+
successBox(config.useYtDlp ? t("settings.ytDlpEnabled") : t("settings.ytDlpDisabled"));
|
|
248
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
249
|
+
await showSettings();
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (action === "ytDlpConnections") {
|
|
254
|
+
const { connections } = await inquirer.prompt([{
|
|
255
|
+
type: "number",
|
|
256
|
+
name: "connections",
|
|
257
|
+
message: t("settings.ytDlpConnectionsPrompt"),
|
|
258
|
+
default: config.ytDlpConnections || 16,
|
|
259
|
+
validate: (input) => (input > 0 && input <= 32) ? true : t("settings.ytDlpConnectionsValidation")
|
|
260
|
+
}]);
|
|
261
|
+
config.ytDlpConnections = connections;
|
|
262
|
+
saveConfig(config);
|
|
263
|
+
successBox(t("settings.ytDlpConnectionsUpdated"));
|
|
264
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
265
|
+
await showSettings();
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
if (action === "downloadDir") {
|
|
271
|
+
const { dir } = await inquirer.prompt([{
|
|
272
|
+
type: "input",
|
|
273
|
+
name: "dir",
|
|
274
|
+
message: t("settings.downloadDirPrompt"),
|
|
275
|
+
default: config.downloadDir,
|
|
276
|
+
validate: (input) => input.trim() !== "" ? true : t("settings.downloadDirValidation")
|
|
277
|
+
}]);
|
|
278
|
+
config.downloadDir = dir;
|
|
279
|
+
saveConfig(config);
|
|
280
|
+
successBox(t("settings.downloadDirUpdated"));
|
|
281
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
282
|
+
await showSettings();
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (action === "retryToggle") {
|
|
287
|
+
config.retryEnabled = !config.retryEnabled;
|
|
288
|
+
successBox(config.retryEnabled ? t("settings.retryEnabled") : t("settings.retryDisabled"));
|
|
289
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
290
|
+
saveConfig(config);
|
|
291
|
+
await showSettings();
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (action === "retrySettings") {
|
|
296
|
+
const { count, delay } = await inquirer.prompt([
|
|
297
|
+
{
|
|
298
|
+
type: "number",
|
|
299
|
+
name: "count",
|
|
300
|
+
message: t("settings.retryCountPrompt"),
|
|
301
|
+
default: config.retryCount,
|
|
302
|
+
validate: (input) => (input >= 0 && input <= 10) ? true : t("settings.retryCountValidation")
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
type: "number",
|
|
306
|
+
name: "delay",
|
|
307
|
+
message: t("settings.retryDelayPrompt"),
|
|
308
|
+
default: config.retryDelay / 1000,
|
|
309
|
+
validate: (input) => (input >= 0) ? true : t("settings.retryDelayValidation")
|
|
310
|
+
}
|
|
311
|
+
]);
|
|
312
|
+
|
|
313
|
+
config.retryCount = count;
|
|
314
|
+
config.retryDelay = delay * 1000;
|
|
315
|
+
|
|
316
|
+
saveConfig(config);
|
|
317
|
+
successBox(t("settings.retryUpdated"));
|
|
318
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
319
|
+
await showSettings();
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import inquirer from "inquirer";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { loadHistory } from "../storage/history.js";
|
|
4
|
+
import { stats } from "./stats.js";
|
|
5
|
+
import { t } from "../../i18n/index.js";
|
|
6
|
+
|
|
7
|
+
export async function showHistory() {
|
|
8
|
+
const history = loadHistory();
|
|
9
|
+
const items = Object.values(history);
|
|
10
|
+
|
|
11
|
+
const completed = items.filter(i => i.completed);
|
|
12
|
+
const inProgress = items.filter(i => !i.completed);
|
|
13
|
+
|
|
14
|
+
console.clear();
|
|
15
|
+
console.log(chalk.bgMagenta.black(` ${t("history.title")} `));
|
|
16
|
+
console.log("");
|
|
17
|
+
|
|
18
|
+
const { type } = await inquirer.prompt([{
|
|
19
|
+
type: "list",
|
|
20
|
+
name: "type",
|
|
21
|
+
message: t("history.selectList"),
|
|
22
|
+
loop: false,
|
|
23
|
+
choices: [
|
|
24
|
+
{ name: t("history.statistics"), value: "stats" },
|
|
25
|
+
{ name: t("history.inProgress", { count: inProgress.length }), value: "progress" },
|
|
26
|
+
{ name: t("history.completed", { count: completed.length }), value: "completed" },
|
|
27
|
+
new inquirer.Separator(),
|
|
28
|
+
{ name: t("history.goBack"), value: "back" }
|
|
29
|
+
]
|
|
30
|
+
}]);
|
|
31
|
+
|
|
32
|
+
if (type === "back") return;
|
|
33
|
+
|
|
34
|
+
if (type === "stats") {
|
|
35
|
+
await stats();
|
|
36
|
+
await showHistory();
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (type === "completed") {
|
|
41
|
+
console.clear();
|
|
42
|
+
console.log(chalk.bgGreen.black(` ${t("history.completedTitle")} `));
|
|
43
|
+
console.log("");
|
|
44
|
+
|
|
45
|
+
if (completed.length === 0) {
|
|
46
|
+
console.log(chalk.yellow(" " + t("history.noCompleted")));
|
|
47
|
+
} else {
|
|
48
|
+
completed.forEach(anime => {
|
|
49
|
+
console.log(` ${chalk.cyan("●")} ${chalk.white(anime.name)} ${chalk.gray(`- ${new Date(anime.lastWatchedAt).toLocaleDateString()}`)}`);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
console.log("");
|
|
53
|
+
|
|
54
|
+
await inquirer.prompt([{ type: "input", name: "dummy", message: t("history.pressEnter") }]);
|
|
55
|
+
await showHistory();
|
|
56
|
+
} else if (type === "progress") {
|
|
57
|
+
if (inProgress.length === 0) {
|
|
58
|
+
console.log(chalk.yellow("\n" + t("history.noWatching")));
|
|
59
|
+
await new Promise(resolve => setTimeout(resolve, 1500));
|
|
60
|
+
await showHistory();
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const { anime } = await inquirer.prompt([{
|
|
65
|
+
type: "list",
|
|
66
|
+
name: "anime",
|
|
67
|
+
message: t("history.selectForDetails"),
|
|
68
|
+
loop: false,
|
|
69
|
+
choices: [
|
|
70
|
+
...inProgress.map(i => ({
|
|
71
|
+
name: `${i.name} ${chalk.gray(`(${t("history.lastEpisode", { episode: i.lastEpisode })})`)}`,
|
|
72
|
+
value: i
|
|
73
|
+
})),
|
|
74
|
+
new inquirer.Separator(),
|
|
75
|
+
{ name: t("history.goBack"), value: "back" }
|
|
76
|
+
]
|
|
77
|
+
}]);
|
|
78
|
+
|
|
79
|
+
if (anime === "back") {
|
|
80
|
+
await showHistory();
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
console.log(chalk.cyan(`\n${anime.name}`));
|
|
85
|
+
console.log(chalk.gray(t("history.lastEpisodeLabel", { episode: anime.lastEpisode })));
|
|
86
|
+
console.log(chalk.gray(t("history.totalEpisodes", { count: anime.totalEpisodes })));
|
|
87
|
+
console.log(chalk.gray(t("history.lastWatchedDate", { date: new Date(anime.lastWatchedAt).toLocaleString() })));
|
|
88
|
+
|
|
89
|
+
await inquirer.prompt([{ type: "input", name: "dummy", message: t("history.pressEnter") }]);
|
|
90
|
+
await showHistory();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import inquirer from "inquirer";
|
|
4
|
+
import { loadHistory } from "../storage/history.js";
|
|
5
|
+
import { t } from "../../i18n/index.js";
|
|
6
|
+
|
|
7
|
+
export async function stats() {
|
|
8
|
+
console.clear();
|
|
9
|
+
const history = loadHistory();
|
|
10
|
+
const animes = Object.values(history);
|
|
11
|
+
|
|
12
|
+
if (animes.length === 0) {
|
|
13
|
+
console.log(chalk.yellow(t("stats.emptyHistory")));
|
|
14
|
+
await inquirer.prompt([{ type: 'input', name: 'devam', message: t("stats.pressEnter") }]);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const totalAnimes = animes.length;
|
|
19
|
+
const completedAnimes = animes.filter(a => a.completed).length;
|
|
20
|
+
const watchingAnimes = totalAnimes - completedAnimes;
|
|
21
|
+
|
|
22
|
+
const totalEpisodes = animes.reduce((acc, curr) => acc + (curr.lastEpisode || 0), 0);
|
|
23
|
+
|
|
24
|
+
const totalMinutes = totalEpisodes * 24;
|
|
25
|
+
const hours = Math.floor(totalMinutes / 60);
|
|
26
|
+
const days = (hours / 24).toFixed(1);
|
|
27
|
+
|
|
28
|
+
const lastWatched = animes.sort((a, b) => new Date(b.lastWatchedAt).getTime() - new Date(a.lastWatchedAt).getTime())[0];
|
|
29
|
+
|
|
30
|
+
console.log(`${chalk.cyan(t("stats.totalAnime"))} ${chalk.bold(totalAnimes)}`);
|
|
31
|
+
console.log(`${chalk.cyan(t("stats.completedAnime"))} ${chalk.green(completedAnimes)}`);
|
|
32
|
+
console.log(`${chalk.cyan(t("stats.watchingAnime"))} ${chalk.yellow(watchingAnimes)}`);
|
|
33
|
+
console.log("");
|
|
34
|
+
console.log(`${chalk.cyan(t("stats.totalEpisodes"))} ${chalk.bold(totalEpisodes)}`);
|
|
35
|
+
console.log(`${chalk.cyan(t("stats.watchTime"))} ${chalk.bold(t("stats.hours", { hours }))} ${chalk.gray(`(${t("stats.days", { days })})`)}`);
|
|
36
|
+
console.log("");
|
|
37
|
+
|
|
38
|
+
if (lastWatched) {
|
|
39
|
+
console.log(`${chalk.cyan(t("stats.lastWatched"))} ${chalk.magenta(lastWatched.name)} ${chalk.gray(`(${timeAgo(new Date(lastWatched.lastWatchedAt))})`)}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
await inquirer.prompt([{ type: 'input', name: 'devam', message: t("stats.pressEnter") }]);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @param {Date} date
|
|
47
|
+
*/
|
|
48
|
+
function timeAgo(date) {
|
|
49
|
+
const seconds = Math.floor((new Date().getTime() - date.getTime()) / 1000);
|
|
50
|
+
|
|
51
|
+
let interval = seconds / 31536000;
|
|
52
|
+
if (interval > 1) return t("stats.timeAgo.years", { count: Math.floor(interval) });
|
|
53
|
+
|
|
54
|
+
interval = seconds / 2592000;
|
|
55
|
+
if (interval > 1) return t("stats.timeAgo.months", { count: Math.floor(interval) });
|
|
56
|
+
|
|
57
|
+
interval = seconds / 86400;
|
|
58
|
+
if (interval > 1) return t("stats.timeAgo.days", { count: Math.floor(interval) });
|
|
59
|
+
|
|
60
|
+
interval = seconds / 3600;
|
|
61
|
+
if (interval > 1) return t("stats.timeAgo.hours", { count: Math.floor(interval) });
|
|
62
|
+
|
|
63
|
+
interval = seconds / 60;
|
|
64
|
+
if (interval > 1) return t("stats.timeAgo.minutes", { count: Math.floor(interval) });
|
|
65
|
+
|
|
66
|
+
return t("stats.timeAgo.justNow");
|
|
67
|
+
}
|
package/start.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import { execSync } from "child_process";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import path from "path";
|
|
5
|
+
|
|
6
|
+
if (!fs.existsSync(path.join(process.cwd(), "node_modules"))) {
|
|
7
|
+
console.log("gerekli moduller eksik, yukleniyor...");
|
|
8
|
+
try {
|
|
9
|
+
execSync("npm install", { stdio: "inherit" });
|
|
10
|
+
console.log("moduller basariyla yuklendi.");
|
|
11
|
+
} catch (error) {
|
|
12
|
+
console.error("modul yukleme hatasi:", error.message);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
import("./src/index.js");
|
|
19
|
+
} catch (error) {
|
|
20
|
+
console.error("uygulama baslatilamadi:", error);
|
|
21
|
+
}
|
package/tanitim-en.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
I'm sure many of us have spent hours on torrent sites trying to download anime, dealing with low-quality links or slow downloads.
|
|
2
|
+
|
|
3
|
+
Now I want to introduce you to an amazing tool that lets you easily watch and download anime on **computer, tablet and phone**,
|
|
4
|
+
|
|
5
|
+
**Animely**:
|
|
6
|
+
|
|
7
|
+
**Animely** is a CLI tool developed with Node.js. It allows you to both watch and download anime from popular platforms like AnimeElysium (animely.net), Animecix (animecix.tv) and AllAnime.
|
|
8
|
+
|
|
9
|
+
* **Key features that set Animely apart from other tools:**
|
|
10
|
+
* **Multiple Source Support**: Turkish (AnimeElysium, Animecix) and English (AllAnime) sources
|
|
11
|
+
* **Multi-Language Support**: Turkish and English interface, automatic source filtering based on selected language
|
|
12
|
+
* **Sub/Dub Option**: Subbed or dubbed viewing option on AllAnime
|
|
13
|
+
* **Instant Streaming**: Start watching with VLC or MPV without waiting for downloads
|
|
14
|
+
* **Resume Playback**: No need to remember where you left off, it automatically saves which episode and minute you stopped at
|
|
15
|
+
* **Quality Selection**: Options like 480p, 720p, 1080p (for Animecix and AllAnime)
|
|
16
|
+
* **Anilist Integration**: Automatically add watched episodes to your Anilist profile
|
|
17
|
+
* **Discord Rich Presence**: Show the anime you're watching live on Discord
|
|
18
|
+
* **Smart Downloads**: Queue system, concurrent downloads, resume capability and more
|
|
19
|
+
* **Aria2 Support**: Ultra-fast downloads with 16 connections for MP4 files
|
|
20
|
+
* **yt-dlp Support**: Fast downloads with parallel connections for M3U8/HLS streams
|
|
21
|
+
|
|
22
|
+
**Just follow these steps to install this amazing tool:**
|
|
23
|
+
**[Hey! First of all, you need to install Node.js!](https://nodejs.org/en/download)**
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
**Installation with NPM (All platforms):**
|
|
27
|
+
```bash
|
|
28
|
+
npm install -g animely
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Installation with AUR (Arch Linux based distros):**
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
yay -S animely
|
|
35
|
+
```
|
|
36
|
+
or,
|
|
37
|
+
```bash
|
|
38
|
+
paru -S animely
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
After installation,
|
|
42
|
+
```bash
|
|
43
|
+
animely
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Done? Not yet! There's also an easy-to-use web version for mobile devices or those who don't want to deal with installation!**:
|
|
47
|
+
**[animely.ewgsta.me](https://animely.ewgsta.me)** just log in with your Discord account after entering the site.
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
**Screenshots & Links:**
|
|
51
|
+
|
|
52
|
+
*Github:* **[Remember this project was developed completely open source, it's very important to show your love and support by leaving a star on the github repo.. :)](https://github.com/ewgsta/animely)**
|
|
53
|
+
|
|
54
|
+
***Enjoy!***
|
|
55
|
+
|
|
56
|
+
CLI
|
|
57
|
+
|
|
58
|
+
https://r2.fakecrime.bio/uploads/10b1f77e-f967-45bf-9a74-e4c539c52dfe.png
|
|
59
|
+
https://r2.fakecrime.bio/uploads/6a01a11a-309c-4d3a-8d40-04ed81e4476b.png
|
|
60
|
+
https://r2.fakecrime.bio/uploads/95ff108b-dc25-44a2-a51a-1457b35ce99c.png
|
|
61
|
+
https://r2.fakecrime.bio/uploads/4f92ad64-056e-4661-bc3a-d7e9def1793f.png
|
|
62
|
+
|
|
63
|
+
Web
|
|
64
|
+
|
|
65
|
+
https://r2.fakecrime.bio/uploads/fe9a4785-77da-4203-ad22-74cb6b171aec.png
|
|
66
|
+
https://r2.fakecrime.bio/uploads/97391eca-3ab0-483d-86e6-0d199be45bde.png
|
package/tanitim-tr.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
Eminim ki birçoğumuz uzun yıllardır anime indirebilmek için torrent sitelerinde saatler harcadık, kalitesiz linklerle veya yavaş indirmelerle uğraştık.
|
|
2
|
+
|
|
3
|
+
Artık **bilgisayar, tablet ve telefon** fark etmeksizin kolayca anime izleyip indirebileceğiniz harika bir aracı tanıtmak istiyorum,
|
|
4
|
+
|
|
5
|
+
**Animely**:
|
|
6
|
+
|
|
7
|
+
**Animely**, Node.js ile geliştirilmiş bir CLI aracıdır. AnimeElysium (animely.net), Animecix (animecix.tv) ve AllAnime gibi popüler platformlar üzerinden animeleri hem izlemenize hem de indirmenize olanak tanır.
|
|
8
|
+
|
|
9
|
+
* **Animely'yi diğer araçlardan ayıran başlıca özellikler:**
|
|
10
|
+
* **Çoklu Kaynak Desteği**: Türkçe (AnimeElysium, Animecix) ve İngilizce (AllAnime) kaynaklar
|
|
11
|
+
* **Çoklu Dil Desteği**: Türkçe ve İngilizce arayüz, seçtiğiniz dile göre otomatik kaynak filtreleme
|
|
12
|
+
* **Sub/Dub Seçeneği**: AllAnime'de altyazılı veya dublajlı izleme imkanı
|
|
13
|
+
* **Anında İzleme**: VLC veya MPV ile indirmeyi beklemeden izlemeye başlayın
|
|
14
|
+
* **Kaldığı Yerden Devam**: Kaldığınız yeri hatırlamak zorunda değilsiniz hangi bölüm hangi dakikada kaldığını otomatik olarak kaydeder
|
|
15
|
+
* **Kalite Seçimi**: 480p, 720p, 1080p gibi seçenekler (Animecix ve AllAnime için)
|
|
16
|
+
* **Anilist Entegrasyonu**: İzlenen bölümleri otomatik Anilist profilinize ekleyin
|
|
17
|
+
* **Discord Rich Presence**: İzlediğiniz animeyi discord'da canlı olarak gösterin
|
|
18
|
+
* **Akıllı İndirme**: Kuyruk sistemi, eşzamanlı indirme ve kaldığı yerden devam etme ve dahası
|
|
19
|
+
* **Aria2 Desteği**: MP4 dosyaları için 16 bağlantı ile ultra hızlı indirme
|
|
20
|
+
* **yt-dlp Desteği**: M3U8/HLS stream'leri için paralel bağlantı ile hızlı indirme
|
|
21
|
+
|
|
22
|
+
**Bu mükemmel aracı kurabilmek için aşağıdaki adımları izlemen yeterli:**
|
|
23
|
+
**[Hey! Her şeyden önce Node.js kurmalısın!](https://nodejs.org/tr/download)**
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
**NPM ile Kurulum (Tüm platformlar):**
|
|
27
|
+
```bash
|
|
28
|
+
npm install -g animely
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**AUR ile Kurulum (Arch Linux tabanlı dağıtımlar):**
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
yay -S animely
|
|
35
|
+
```
|
|
36
|
+
veya,
|
|
37
|
+
```bash
|
|
38
|
+
paru -S animely
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Kurulumdan sonra,
|
|
42
|
+
```bash
|
|
43
|
+
animely
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Bitti mi? Bitmedi! Mobil cihazlar veya kurulumla uğraşmak istemeyenler için kullanımı kolay web versiyonu da mevcut!**:
|
|
47
|
+
**[animely.ewgsta.me](https://animely.ewgsta.me)** siteye girdikten sonra discord hesabınız ile giriş yapmanız yeterli.
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
**Görseller & Bağlantılar:**
|
|
51
|
+
|
|
52
|
+
*Github:* **[Unutmayın bu proje tamamen açık kaynaklı bir şekilde geliştirildi, github deposuna star bırakarak projeyi sevdiğinizi ve desteklediğinizi göstermeniz çok önemli.. :)](https://github.com/ewgsta/animely)**
|
|
53
|
+
|
|
54
|
+
***İyi eğlenceler!***
|
|
55
|
+
|
|
56
|
+
CLI
|
|
57
|
+
|
|
58
|
+
https://r2.fakecrime.bio/uploads/10b1f77e-f967-45bf-9a74-e4c539c52dfe.png
|
|
59
|
+
https://r2.fakecrime.bio/uploads/6a01a11a-309c-4d3a-8d40-04ed81e4476b.png
|
|
60
|
+
https://r2.fakecrime.bio/uploads/95ff108b-dc25-44a2-a51a-1457b35ce99c.png
|
|
61
|
+
https://r2.fakecrime.bio/uploads/4f92ad64-056e-4661-bc3a-d7e9def1793f.png
|
|
62
|
+
|
|
63
|
+
Web
|
|
64
|
+
|
|
65
|
+
https://r2.fakecrime.bio/uploads/fe9a4785-77da-4203-ad22-74cb6b171aec.png
|
|
66
|
+
https://r2.fakecrime.bio/uploads/97391eca-3ab0-483d-86e6-0d199be45bde.png
|