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,338 @@
1
+ // @ts-check
2
+ import inquirer from "inquirer";
3
+ import chalk from "chalk";
4
+ import axios from "axios";
5
+ import path from "path";
6
+ import { getConfig } from "../../utils/storage/config.js";
7
+ import { loadHistory } from "../../utils/storage/history.js";
8
+ import { setActivity } from "../../utils/discord.js";
9
+ import { spinner } from "../../utils/spinner.js";
10
+ import { menuHeader } from "../../utils/ui/box.js";
11
+ import { searchAnimes } from "../../utils/search.js";
12
+ import { formatName, getLink } from "../../functions/episodes.js";
13
+ import { API_URL } from "../../constants.js";
14
+ import { t } from "../../i18n/index.js";
15
+ import {
16
+ askSelectionMethod,
17
+ parseRange,
18
+ askDownloadAction,
19
+ downloadEpisodes,
20
+ playEpisode,
21
+ postWatchActions,
22
+ addToQueue,
23
+ buildEpisodeChoices
24
+ } from "./base.js";
25
+
26
+ /**
27
+ * Animely kaynağı için arama ve indirme
28
+ * @param {import("../../jsdoc.js").Anime[]} animes
29
+ * @param {Array<any>} downloadQueue
30
+ */
31
+ export async function handleAnimely(animes, downloadQueue) {
32
+ let selectedAnime;
33
+ let episodes;
34
+
35
+ // Anime arama döngüsü
36
+ while (true) {
37
+ console.clear();
38
+ const { name } = await inquirer.prompt([{
39
+ type: "input",
40
+ name: "name",
41
+ message: t("search.enterName"),
42
+ validate: (input) => {
43
+ if (!input || input.trim() === "") {
44
+ return t("search.invalidName");
45
+ }
46
+ return true;
47
+ },
48
+ }]);
49
+
50
+ if (name.toLowerCase() === t("search.cancel") || name.toLowerCase() === "iptal" || name.toLowerCase() === "cancel") {
51
+ return;
52
+ }
53
+
54
+ spinner.start();
55
+
56
+ const foundAnimes = searchAnimes(name, animes);
57
+
58
+ if (foundAnimes.length === 0) {
59
+ spinner.fail(chalk.gray(t("search.notFound")));
60
+ await new Promise(resolve => setTimeout(resolve, 1500));
61
+ continue;
62
+ }
63
+
64
+ if (foundAnimes.length === 1) {
65
+ selectedAnime = foundAnimes[0];
66
+ } else {
67
+ spinner.stop();
68
+
69
+ console.log(chalk.yellow(`\n${t("search.foundCount", { count: foundAnimes.length })}`));
70
+
71
+ const { anime } = await inquirer.prompt([{
72
+ type: "list",
73
+ name: "anime",
74
+ message: t("search.selectAnime"),
75
+ pageSize: 15,
76
+ loop: false,
77
+ choices: [
78
+ { name: t("search.goBack"), value: "back" },
79
+ new inquirer.Separator(),
80
+ ...foundAnimes.map(anime => ({
81
+ name: `${anime.NAME} ${chalk.gray(`(${t("search.season")} ${anime.SEASON_NUMBER}, ${anime.TOTAL_EPISODES} ${t("search.episodes")})`)}`,
82
+ value: anime,
83
+ }))
84
+ ],
85
+ }]);
86
+
87
+ if (anime === "back") continue;
88
+
89
+ selectedAnime = anime;
90
+ spinner.start();
91
+ }
92
+
93
+ // Bölümleri getir
94
+ let httpData;
95
+ try {
96
+ httpData = await axios.post(`${API_URL}/searchAnime`, { payload: selectedAnime.SLUG });
97
+ } catch (error) {
98
+ spinner.fail(chalk.red(t("errors.episodesFailed")));
99
+ console.error(chalk.gray(t("app.errorDetail", { message: error.message })));
100
+ await new Promise(resolve => setTimeout(resolve, 2000));
101
+ continue;
102
+ }
103
+
104
+ episodes = httpData.data.episodes;
105
+
106
+ if (!episodes || episodes.length === 0) {
107
+ spinner.fail(chalk.gray(t("episodes.noEpisodes")));
108
+ await new Promise(resolve => setTimeout(resolve, 1500));
109
+ continue;
110
+ }
111
+
112
+ spinner.stop();
113
+ break;
114
+ }
115
+
116
+ // Anime detayları
117
+ const config = getConfig();
118
+ console.clear();
119
+
120
+ if (config.showAnimeDetails !== false) {
121
+ console.log(chalk.bgCyan.black(` ${selectedAnime.NAME} \n\n`));
122
+ console.log(` ${chalk.gray(t("episodes.episode") + ":")} ${selectedAnime.TOTAL_EPISODES} ${chalk.gray("| " + t("search.season") + ":")} ${selectedAnime.SEASON_NUMBER}`);
123
+
124
+ if (selectedAnime.CATEGORIES && selectedAnime.CATEGORIES.length > 0) {
125
+ console.log(` ${chalk.gray("Kategoriler:")} ${selectedAnime.CATEGORIES.join(", ")}`);
126
+ }
127
+
128
+ if (selectedAnime.DESCRIPTION || selectedAnime.SYNOPSIS) {
129
+ const desc = selectedAnime.DESCRIPTION || selectedAnime.SYNOPSIS;
130
+ console.log(chalk.italic.gray(` ${desc.substring(0, 150)}${desc.length > 150 ? "..." : ""}`));
131
+ }
132
+
133
+ console.log("");
134
+ } else {
135
+ console.log(chalk.bgCyan.black(` ${selectedAnime.NAME} `) + chalk.gray(` ${t("episodes.episodeCount", { count: episodes.length })}`));
136
+ console.log("");
137
+ }
138
+
139
+ // Ana menü
140
+ while (true) {
141
+ const { action } = await inquirer.prompt([{
142
+ type: "list",
143
+ name: "action",
144
+ message: t("episodes.selectAction"),
145
+ choices: [
146
+ { name: t("episodes.watch"), value: "watch" },
147
+ { name: t("episodes.download"), value: "download" },
148
+ { name: t("episodes.goBack"), value: "back" }
149
+ ]
150
+ }]);
151
+
152
+ if (action === "back") return;
153
+
154
+ if (action === "watch") {
155
+ await watchEpisode(selectedAnime, episodes);
156
+ } else if (action === "download") {
157
+ await downloadEpisodesFlow(selectedAnime, episodes, downloadQueue);
158
+ }
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Bölüm izleme
164
+ */
165
+ async function watchEpisode(selectedAnime, episodes) {
166
+ const history = loadHistory();
167
+ const animeHistory = history[selectedAnime.NAME];
168
+ const lastWatchedEp = animeHistory?.lastEpisode || 0;
169
+
170
+ while (true) {
171
+ console.clear();
172
+ menuHeader(selectedAnime.NAME, lastWatchedEp, episodes.length);
173
+
174
+ const choices = buildEpisodeChoices(episodes, lastWatchedEp, (ep) => {
175
+ const links = [ep.backblaze_link, ep.watch_link_1, ep.watch_link_2, ep.watch_link_3];
176
+ const hasValidLink = !links.every((link) => !link || link.trim() === "");
177
+
178
+ return {
179
+ name: formatName(ep.episode_number, ep.type),
180
+ value: {
181
+ id: ep.id,
182
+ episode_number: ep.episode_number,
183
+ link: getLink(links),
184
+ },
185
+ disabled: !hasValidLink
186
+ };
187
+ });
188
+
189
+ const { episode } = await inquirer.prompt([{
190
+ type: "list",
191
+ name: "episode",
192
+ message: t("episodes.selectEpisode"),
193
+ pageSize: 15,
194
+ loop: false,
195
+ choices
196
+ }]);
197
+
198
+ if (episode === "back") break;
199
+
200
+ if (!episode.link) {
201
+ console.log(chalk.red(t("player.sourceNotFound")));
202
+ await new Promise(resolve => setTimeout(resolve, 1500));
203
+ continue;
204
+ }
205
+
206
+ try {
207
+ await playEpisode({
208
+ animeName: selectedAnime.NAME,
209
+ animeImage: selectedAnime.FIRST_IMAGE,
210
+ episodeNumber: typeof episode.episode_number === "number"
211
+ ? episode.episode_number
212
+ : parseInt(episode.episode_number, 10),
213
+ totalEpisodes: episodes.length,
214
+ streamUrl: episode.link,
215
+ supportResume: true
216
+ });
217
+
218
+ await postWatchActions({
219
+ animeName: selectedAnime.NAME,
220
+ episodeNumber: typeof episode.episode_number === "number"
221
+ ? episode.episode_number
222
+ : parseInt(episode.episode_number, 10),
223
+ totalEpisodes: episodes.length,
224
+ existingAnilistId: animeHistory?.anilistId
225
+ });
226
+
227
+ } catch (error) {
228
+ console.error(chalk.red(t("player.playerError", { message: error.message })));
229
+ await new Promise(resolve => setTimeout(resolve, 2000));
230
+ }
231
+ }
232
+ }
233
+
234
+ /**
235
+ * Bölüm indirme akışı
236
+ */
237
+ async function downloadEpisodesFlow(selectedAnime, episodes, downloadQueue) {
238
+ setActivity(`${selectedAnime.NAME}`, t("progress.downloading"));
239
+
240
+ const selectionMethod = await askSelectionMethod(selectedAnime.NAME);
241
+ if (selectionMethod === "back") return;
242
+
243
+ let selectedEpisodes = [];
244
+
245
+ if (selectionMethod === "list") {
246
+ console.clear();
247
+ console.log(chalk.bgCyan.black(` ${t("download.episodeSelection", { name: selectedAnime.NAME })} `));
248
+ console.log("");
249
+
250
+ const { selected } = await inquirer.prompt([{
251
+ type: "checkbox",
252
+ name: "selected",
253
+ message: t("download.selectEpisodes"),
254
+ choices: episodes.map(ep => {
255
+ const links = [ep.backblaze_link, ep.watch_link_1, ep.watch_link_2, ep.watch_link_3];
256
+ const hasValidLink = !links.every((link) => !link || link.trim() === "");
257
+
258
+ return {
259
+ name: formatName(ep.episode_number, ep.type),
260
+ value: {
261
+ id: ep.id,
262
+ episode_number: ep.episode_number,
263
+ link: getLink(links),
264
+ },
265
+ disabled: !hasValidLink
266
+ };
267
+ })
268
+ }]);
269
+ selectedEpisodes = selected;
270
+
271
+ } else if (selectionMethod === "range") {
272
+ console.clear();
273
+ console.log(chalk.bgCyan.black(` ${t("download.rangeSelection", { name: selectedAnime.NAME })} `));
274
+ console.log("");
275
+
276
+ const { range } = await inquirer.prompt([{
277
+ type: "input",
278
+ name: "range",
279
+ message: t("download.enterRangePrompt"),
280
+ validate: (input) => {
281
+ if (!input || input.trim() === "") return t("download.invalidRange");
282
+ return true;
283
+ }
284
+ }]);
285
+
286
+ const numbers = parseRange(range);
287
+ selectedEpisodes = episodes.filter(ep => {
288
+ const epNum = typeof ep.episode_number === "number" ? ep.episode_number : parseInt(ep.episode_number, 10);
289
+ return numbers.has(epNum);
290
+ }).map(ep => {
291
+ const links = [ep.backblaze_link, ep.watch_link_1, ep.watch_link_2, ep.watch_link_3];
292
+ return {
293
+ id: ep.id,
294
+ episode_number: ep.episode_number,
295
+ link: getLink(links)
296
+ };
297
+ });
298
+
299
+ } else if (selectionMethod === "all") {
300
+ selectedEpisodes = episodes.map(ep => {
301
+ const links = [ep.backblaze_link, ep.watch_link_1, ep.watch_link_2, ep.watch_link_3];
302
+ return {
303
+ id: ep.id,
304
+ episode_number: ep.episode_number,
305
+ link: getLink(links)
306
+ };
307
+ });
308
+ }
309
+
310
+ if (!selectedEpisodes || selectedEpisodes.length === 0) {
311
+ console.log(chalk.yellow(t("download.noEpisodeSelected")));
312
+ return;
313
+ }
314
+
315
+ console.clear();
316
+
317
+ const downloadAction = await askDownloadAction();
318
+ const config = getConfig();
319
+ const safeAnimeName = selectedAnime.NAME.replace(/[<>:"/\\|?*]/g, "").trim();
320
+ const dirPath = path.join(config.downloadDir, safeAnimeName);
321
+
322
+ if (downloadAction === "queue") {
323
+ addToQueue({
324
+ animeName: selectedAnime.NAME,
325
+ episodes: selectedEpisodes,
326
+ dirPath,
327
+ downloadQueue
328
+ });
329
+ await new Promise(resolve => setTimeout(resolve, 1000));
330
+ return;
331
+ }
332
+
333
+ await downloadEpisodes({
334
+ animeName: selectedAnime.NAME,
335
+ episodes: selectedEpisodes,
336
+ dirPath
337
+ });
338
+ }