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.
Files changed (54) hide show
  1. package/.github/workflows/releases.yml +39 -0
  2. package/LICENSE +400 -0
  3. package/README-EN.md +134 -0
  4. package/README.md +134 -0
  5. package/aur/.SRCINFO +16 -0
  6. package/aur/PKGBUILD +43 -0
  7. package/eslint.config.js +49 -0
  8. package/jsconfig.json +9 -0
  9. package/package.json +45 -0
  10. package/src/constants.js +13 -0
  11. package/src/functions/episodes.js +38 -0
  12. package/src/functions/time.js +27 -0
  13. package/src/functions/variables.js +3 -0
  14. package/src/i18n/en.json +351 -0
  15. package/src/i18n/index.js +80 -0
  16. package/src/i18n/tr.json +348 -0
  17. package/src/index.js +307 -0
  18. package/src/jsdoc.js +72 -0
  19. package/src/sources/allanime.js +195 -0
  20. package/src/sources/animecix.js +223 -0
  21. package/src/sources/animely.js +100 -0
  22. package/src/sources/handlers/allanime.js +318 -0
  23. package/src/sources/handlers/animecix.js +316 -0
  24. package/src/sources/handlers/animely.js +338 -0
  25. package/src/sources/handlers/base.js +391 -0
  26. package/src/sources/handlers/index.js +4 -0
  27. package/src/sources/index.js +80 -0
  28. package/src/utils/anilist.js +193 -0
  29. package/src/utils/data_manager.js +27 -0
  30. package/src/utils/discord.js +86 -0
  31. package/src/utils/download/concurrency.js +27 -0
  32. package/src/utils/download/download.js +485 -0
  33. package/src/utils/download/progress.js +84 -0
  34. package/src/utils/players/mpv.js +251 -0
  35. package/src/utils/players/vlc.js +120 -0
  36. package/src/utils/process_queue.js +121 -0
  37. package/src/utils/resume_watch.js +137 -0
  38. package/src/utils/search.js +39 -0
  39. package/src/utils/search_download.js +21 -0
  40. package/src/utils/speedtest.js +30 -0
  41. package/src/utils/spinner.js +7 -0
  42. package/src/utils/storage/cache.js +42 -0
  43. package/src/utils/storage/config.js +71 -0
  44. package/src/utils/storage/history.js +69 -0
  45. package/src/utils/storage/queue.js +43 -0
  46. package/src/utils/storage/watch_progress.js +104 -0
  47. package/src/utils/system.js +176 -0
  48. package/src/utils/ui/box.js +140 -0
  49. package/src/utils/ui/settings_ui.js +322 -0
  50. package/src/utils/ui/show_history.js +92 -0
  51. package/src/utils/ui/stats.js +67 -0
  52. package/start.js +21 -0
  53. package/tanitim-en.md +66 -0
  54. 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