ani-cli-npm 1.4.2 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
package/bin/index.js CHANGED
@@ -1,580 +1,98 @@
1
- #! /usr/bin/env node
2
-
3
- const {emitWarning} = process;
4
-
5
- process.emitWarning = (warning, ...args) => {
6
- if (args[0] === 'ExperimentalWarning') {
7
- return;
8
- }
9
-
10
- if (args[0] && typeof args[0] === 'object' && args[0].type === 'ExperimentalWarning') {
11
- return;
12
- }
13
-
14
- return emitWarning(warning, ...args);
1
+ "use strict";
2
+ // process.removeAllListeners() // Ignore warning
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
15
5
  };
16
- const fetch = require('node-fetch');
17
- const PlayerController = require("media-player-controller")
18
- const open = require("open")
19
- const prompt = require("simple-input");
20
- const getAppDataPath = require("appdata-path")
21
- const fs = require("fs")
22
- const downloadsFolder = require('downloads-folder');
23
- const dl = require("download-file-with-progressbar");
24
-
25
-
26
-
27
- let config = {
28
- player: "BROWSER",
29
- proxy: "",
30
- user_agent: "Mozilla/5.0 (X11; Linux x86_64; rv:99.0) Gecko/20100101 Firefox/100.0",
31
- most_recent: {
32
- episode_number: 0,
33
- anime_id: "",
34
- episodes: []
35
- },
36
- download_folder: downloadsFolder()
37
- }
38
-
39
-
40
- function read_config(){
41
- try{
42
- try {
43
- config = JSON.parse(fs.readFileSync(getAppDataPath() + "/ani-cli-npm.conf")) //getAppDataPath()
44
- if (!config.hasOwnProperty("player")) config.player = "BROWSER";
45
- if (!config.hasOwnProperty("user_agent")) config.user_agent = "Mozilla/5.0 (X11; Linux x86_64; rv:99.0) Gecko/20100101 Firefox/100.0";
46
- if (!config.hasOwnProperty("proxy")) config.user_agent = "";
47
- if (!config.hasOwnProperty("most_recent")) config.most_recent = null;
48
- if(!config.download_folder) config.download_folder = downloadsFolder();
49
- fs.writeFileSync(getAppDataPath() + "/ani-cli-npm.conf", JSON.stringify(config))
50
- } catch {
51
- fs.writeFileSync(getAppDataPath() + "/ani-cli-npm.conf", JSON.stringify(config))
52
- }
53
- }catch{
54
- console.log(colors.Red, "Failed to read config file")
55
- }
56
- }
57
-
58
- function write_config(){
59
- try{
60
- fs.writeFileSync(getAppDataPath() + "/ani-cli-npm.conf", JSON.stringify(config))
61
- }catch{
62
- console.log(colors.Red, "Failed to write to config file")
63
- }
64
- }
65
-
66
-
67
-
68
- const gogohd_url="https://gogohd.net/"
69
- const base_url="https://animixplay.to"
70
- const colors = {
71
- Black: "\x1b[30m%s\x1b[0m",
72
- Red: "\x1b[31m%s\x1b[0m",
73
- Green: "\x1b[32m%s\x1b[0m",
74
- Yellow: "\x1b[33m%s\x1b[0m",
75
- Blue: "\x1b[34m%s\x1b[0m",
76
- Magenta: "\x1b[35m%s\x1b[0m",
77
- Cyan: "\x1b[36m%s\x1b[0m",
78
- White: "\x1b[37m%s\x1b[0m"
79
- }
80
-
81
-
82
- //const HttpsProxyAgent = require('https-proxy-agent');
83
- //let proxyAgent = new HttpsProxyAgent(config.proxy);
84
-
85
-
86
- async function config_(temp){
87
- console.clear()
88
- console.log(colors.Blue, "ANI-CLI-NPM \n")
89
- console.log(colors.Yellow, "Config:\n")
90
- console.log(colors.Cyan, `1) Player; ${temp.player}`)
91
- console.log(colors.Cyan, `2) Proxy; ${temp.proxy}`)
92
- console.log(colors.Cyan, `3) User agent; ${temp.user_agent}`)
93
- console.log(colors.Cyan, `4) Download folder; ${config.download_folder}`)
94
- console.log(colors.Cyan, "5) Save and exit")
95
- console.log(colors.Cyan, "6) Exit without saving changes")
96
- let choice = parseInt(await input(""));
97
- switch (choice){
98
- case 1:
99
- console.log(colors.Cyan, `1) VLC (default)`)
100
- console.log(colors.Cyan, `2) Browser`)
101
- console.log(colors.Cyan, `3) MPV`)
102
- let player = parseInt(await input("New Player;"))
103
- switch (player){
104
- case 1:
105
- temp.player = "VLC"
106
- break
107
- case 2:
108
- temp.player = "BROWSER"
109
- break
110
- case 3:
111
- temp.player = "MPV"
112
- break
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ // External
8
+ const appdata_path_1 = __importDefault(require("appdata-path"));
9
+ const chalk = require("chalk");
10
+ // Internal
11
+ const Anime_1 = require("./Anime");
12
+ const search_anime_1 = require("./search_anime");
13
+ const load_config_1 = require("./load_config");
14
+ const input_1 = require("./input");
15
+ const change_config_1 = require("./change_config");
16
+ const download_1 = require("./download");
17
+ const fs_1 = __importDefault(require("fs"));
18
+ const app_data_folder = (0, appdata_path_1.default)();
19
+ const cache_folder = app_data_folder + "/ani-cli-npm";
20
+ console.clear();
21
+ async function main() {
22
+ let config = (0, load_config_1.load_config)(app_data_folder);
23
+ console.log(chalk.magenta("Ani-cli-npm!\n"));
24
+ if (config.most_recent.anime_id !== "") {
25
+ console.log(chalk.grey(`Most recently played: ${config.most_recent.anime_id} episode ${config.most_recent.episode_number + 1}\n`));
26
+ }
27
+ let choice = await (0, input_1.selection)([
28
+ "Search",
29
+ "Continue",
30
+ "Download",
31
+ "Option",
32
+ "Quit",
33
+ ], ["s", "c", "d", "o", "q"], ((thing) => { return chalk.magenta(thing); }), ((thing) => { return chalk.magenta(thing); }));
34
+ switch (choice) {
35
+ case 0: // Search
36
+ let anime_id = await (0, search_anime_1.search)();
37
+ let anime = new Anime_1.Anime();
38
+ await anime.init(anime_id, cache_folder);
39
+ console.log(`Select episode [1-${anime.episode_list.length}]`);
40
+ let episode_number = await (0, input_1.number_input)(anime.episode_list.length) - 1;
41
+ await anime.play_head(episode_number, config, cache_folder);
42
+ await anime.player.quit();
43
+ await main();
44
+ break;
45
+ case 1: // Continue
46
+ if (config.most_recent.anime_id == "") {
47
+ console.clear();
48
+ console.log(chalk.red("No episode played recently"));
49
+ await main();
50
+ break;
113
51
  }
114
- return temp,0
115
- case 2:
116
- temp.proxy = (await(input("New Proxy;"))).replaceAll(" ", "")
117
- return temp, 0
118
- case 3:
119
- temp.user_agent = await(input("New User agent;"))
120
- return temp, 0
121
- case 4:
122
- temp.download_folder = await(input("New download folder: "))
123
- return temp, 0
124
- case 5:
125
- return temp, 1
126
- case 6:
127
- return temp, 2
128
- }
129
- }
130
-
131
- async function input(message){
132
- if (message){
133
- console.log(colors.Magenta,message)
134
- }
135
- return await prompt(">")
136
- }
137
-
138
- async function curl(url, method="GET", redirect = false){
139
- //try{
140
- let response = await fetch(url, {
141
- //"agent": proxyAgent,
142
- "headers": {
143
- 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:99.0) Gecko/20100101 Firefox/100.0',
144
- "X-Requested-With": "XMLHttpRequest"
145
- },
146
- "referrerPolicy": "origin",
147
- "body": null,
148
- "method": method,
149
- "redirect": 'follow',
150
- "follow": 10,
151
- }).catch(async function(err) {
152
- console.warn(colors.Red, `Something went wrong connecting to ${url}.`);
153
- await search();
154
- process.exit()
155
- })
156
- if (redirect){
157
- return response.url
158
- }else{
159
- return await response.text()
160
- }
161
- /*}catch{
162
- console.log(colors.Red, "Something went wrong in curl()")
163
- await main()
164
- }*/
165
-
166
- }
167
-
168
- async function _continue(){
169
- let link = await get_video_link(config.most_recent.episodes[config.most_recent.episode_number])
170
- await play(link, config.most_recent)
171
- process.exit()
172
- }
173
-
174
-
175
- function RegexParse(str, rule) {
176
- let escapeRegex = (str) => str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
177
- return new RegExp("^" + rule.split("*").map(escapeRegex).join(".*") + "$").test(str);
178
- }
179
-
180
- async function search_anime(search){
181
- let filter = "*<ahref=\"/category/*\"title=\"*\">"
182
- let html = (await curl("https://gogoanime.dk//search.html?keyword="+search)).split("\n")
183
- let lines = []
184
- for (x in html){
185
- html[x] = html[x].replaceAll(/ /g,'').replaceAll(/\t/g,'')
186
- if (RegexParse(html[x], filter)){
187
- html[x] = html[x].slice(html[x].indexOf("/category/")+10);
188
- html[x] = html[x].slice(0, html[x].indexOf("\"title="));
189
- lines.push(html[x])
190
- }
191
- }
192
- if (!lines[0]){
193
- lines.pop()
194
- }
195
-
196
-
197
- return lines
198
- }
199
-
200
- async function episode_list(anime_id){
201
- let html = (await curl(base_url+"/v1/"+anime_id)).split("\n")
202
- let lines = ""
203
-
204
- for (let x in html){
205
- if(RegexParse(html[x], "*<div id=\"epslistplace\"*")){
206
- lines = (html[x])
207
- }
208
- }
209
-
210
- lines = lines.slice(55, lines.length).replace("}</div>", "")
211
- lines = "{" + lines.slice(lines.indexOf(",")+1, lines.length) + "}"
212
- lines = JSON.parse(lines)
213
-
214
- let json = []
215
- for (x in lines){
216
- json.push(lines[x])
217
- }
218
- return json
219
- }
220
-
221
- function download(link, file_name){
222
- console.log(colors.Red, "Warning: Animixplay will download an m3u8 file. This will require some extra steps to play. It is advised to use a 3rd party website or tool to download these from the link.")
223
- let option = {
224
- filename: link.includes("m3u8") ? file_name.replace("mp4", "m3u8") : file_name,
225
- dir: config.download_folder,
226
- onDone: (info)=>{
227
- console.log(colors.Green, `\n -- Download finished -- \nLocation: ${info.path}\nSize: ${Math.round(info.size/100000)*10} Bytes`);
228
- return 0;
229
- },
230
- onError: (err) => {
231
- console.log(colors.Red, 'error', err);
232
- },
233
- onProgress: (curr, total) => {
234
- process.stdout.clearLine(0);
235
- process.stdout.cursorTo(0);
236
- process.stdout.write(('\x1b[32m -- progress '+ (curr / total * 100).toFixed(2) + '% -- \x1b[0m'));
237
- },
238
- }
239
- console.log(colors.White, `${option.dir}/${option.filename}`)
240
-
241
- return dl(link, option);
242
-
243
- }
244
-
245
- async function selection(options, prompt, extra_options = []){
246
- let selection = 0
247
- while (true){
248
- selection = (await input(prompt))
249
- if ((selection <= options && selection >= 1) || extra_options.includes(selection)){
250
- break
251
- }
252
- console.log(colors.Red,`Please input a valid option.`)
253
- }
254
- return selection
255
- }
256
-
257
- async function process_search(query) {
258
- console.log(colors.Yellow, "Searching: "+query)
259
-
260
- let search_results = await search_anime(query)
261
- if (!search_results[0]) {
262
- console.log(colors.Red, "No results.")
263
- await main()
264
- process.exit()
265
- } else {
266
- for (x in search_results) {
267
- console.log(colors.Cyan,`${parseInt(x)+1})${" ".repeat(((search_results.length).toString().length+1)-((parseInt(x)+1).toString().length))}${search_results[x].replaceAll("-", " ")}`)
268
- }
269
- }
270
-
271
- let anime_id = search_results[await selection(search_results.length, "Please select an anime.")-1]
272
- let episodes = await episode_list(anime_id)
273
- let episode_number = 0;
274
- if (episodes.length > 1){
275
- episode_number = (await selection(episodes.length, `Please select an episode (1-${episodes.length}).`))-1
276
- }
277
- return {
278
- anime_id: anime_id,
279
- episodes: episodes,
280
- episode_number: episode_number
281
- }
282
- }
283
-
284
- async function get_video_link(episode_dpage){
285
- let id = episode_dpage.replace("//gogohd.net/streaming.php?id=","")
286
- id = id.slice(0, id.indexOf("="))
287
- let link = await generate_link(1,id)
288
- if (!link){
289
- link = await generate_link(2,id)
290
- }
291
- return link
292
- }
293
-
294
- async function generate_link(provider, id){
295
- let html = ""
296
- let provider_name = ""
297
- switch (provider) {
298
- case 1:
299
- html = await curl(`${gogohd_url}streaming.php?id=${id}`)
300
- provider_name = 'Xstreamcdn'
301
- console.log(colors.Yellow, `Fetching ${provider_name} links...`)
302
- html = html.split("\n")
303
- let fb_id = ""
304
- for (x in html){
305
- if (RegexParse(html[x], "*<li class=\"linkserver\" data-status=\"1\" data-video=\"https://fembed9hd.com/v/*")){
306
- fb_id = html[x].slice(html[x].indexOf("/v/")+3, html[x].indexOf("\">X"))
307
- break
308
- }
309
- }
310
- if (!fb_id){
311
- console.log("Error, no fb_id found.")
312
- return 0
313
- }
314
-
315
- //let refr = "https://fembed-hd.com/v/"+fb_id
316
- let post = await curl("https://fembed-hd.com/api/source/"+fb_id, "POST")
317
- post = post.slice(post.indexOf(",\"data\":[{\"file\":\"")+18, post.length)
318
- post = post.slice(0, post.indexOf("\"")).replaceAll("\\/","/")
319
- return post
320
- case 2:
321
- provider_name = 'Animixplay'
322
- let buffer = new Buffer(id)
323
- let enc_id = buffer.toString("base64")
324
- buffer = new Buffer(id+"LTXs3GrU8we9O"+enc_id)
325
- let ani_id = buffer.toString("base64")
326
- buffer = Buffer.from((await curl(`${base_url}/api/live${ani_id}`, "GET", true)).split("#")[1], "base64")
327
- if (config.player === "BROWSER"){
328
- return `${base_url}/api/live${ani_id}`
329
- }
330
- return buffer.toString("utf-8") //TODO m3u8 player
331
-
332
-
333
- }
334
- }
335
-
336
- async function post_play(link, anime, player = null){
337
- while (true){
338
- config.most_recent = anime
339
- write_config()
340
-
341
- console.log(colors.Yellow, `Playing episode ${anime.episode_number+1} of ${anime.anime_id.replaceAll("-", " ")}\n`)
342
- console.log(colors.Cyan, "1/l) Show Link")
343
- console.log(colors.Cyan, "2/n) Next Episode")
344
- console.log(colors.Cyan, "3/p) Prev Episode")
345
- console.log(colors.Cyan, "4/d) Download")
346
- console.log(colors.Cyan, "5/q) Quit")
347
- switch (await selection(5, "select;", ["l", "n", "p", "d", "q"])){
348
- case "l":
349
- case "1":
350
- console.log(colors.Yellow, "Link: "+link)
351
- break
352
- case "n":
353
- case "2":
354
- if (anime.episode_number > anime.episodes.length-2){
355
- console.log(colors.Red, "Damn, all out of episodes?")
356
- break
357
- }
358
- anime.episode_number += 1
359
- link = await get_video_link(anime.episodes[anime.episode_number])
360
- await play(link, anime, player)
361
- process.exit()
362
- break
363
- //EVEN MORE NEEDLESS QUIT STATEMENTS!!!!!!
364
- case "p":
365
- case "3":
366
- if (anime.episode_number < 2){
367
- console.log(colors.Red, "Error; Episode 0 is only available for premium members")
368
- break
369
- }
370
- anime.episode_number -= 1
371
- link = await get_video_link(anime.episodes[anime.episode_number])
372
- await play(link, anime, player)
373
- process.exit()
374
- break
375
- case "d":
376
- case "4":
377
- console.log(colors.Yellow, "Beware of the dysfunctional await clause.")
378
- await download(link, anime.anime_id+(anime.episode_number+1)+".mp4")
379
- post_play(link, anime, player)
380
- break
381
- case "q":
382
- case "5":
383
- console.clear()
384
- await main()
385
- process.exit()
386
- break
387
- }
388
- }
389
- }
390
-
391
- async function play(link, anime, player){
392
- console.clear()
393
- console.log(colors.Blue, "ANI-CLI-NPM \n")
394
- if (config.player === "VLC"){
395
- if (!player){
396
- console.log(colors.Yellow, "Loading VLC... ")
397
- player = new PlayerController({
398
- app: 'vlc',
399
- args: ['--fullscreen'],
400
- media: link
401
- });
402
- await player.launch(err => {
403
- if (err) return console.error(err.message);
404
- });
405
- }else{
406
- player.quit()
407
- console.log(colors.Yellow, "Loading VLC... ")
408
- player = new PlayerController({
409
- app: 'vlc',
410
- args: ['--fullscreen'],
411
- media: link
412
- });
413
- await player.launch(err => {
414
- if (err) return console.error(err.message);
415
- });
416
- }
417
-
418
- await post_play(link, anime, player)
419
- process.exit()
420
-
421
-
422
- }else if (config.player === "BROWSER"){
423
- console.log(colors.Yellow, "Opening video in browser... ")
424
- open(link)
425
- await post_play(link, anime)
426
- process.exit()
427
- }else if (config.player === "MPV"){
428
- if (!player){
429
- console.log(colors.Yellow, "Loading MPV... ")
430
- player = new PlayerController({
431
- app: 'mpv',
432
- args: ['--fullscreen'],
433
- media: link
434
- });
435
- await player.launch(err => {
436
- if (err) return console.error(err.message);
437
- });
438
- }else{
439
- player.load(link)
440
- }
441
-
442
- await post_play(link, anime, player)
443
- process.exit()
444
- }
445
- }
446
-
447
- async function search(){
448
- console.log(colors.Magenta, "Search...")
449
- let choice = await input("")
450
- let anime = await process_search(choice)
451
-
452
- console.log("\n")
453
-
454
- console.log(colors.Blue, "Indexing video...")
455
- let link = await get_video_link(anime.episodes[anime.episode_number])
456
- if (!link){
457
- console.log(colors.Red, "Np link for this episode found?")
458
- console.log(colors.Yellow, "^ at current this is due to not all of the required video repos being implemented.")
459
- console.log(colors.Yellow, "Sorry for any inconvenience, this should soon by implemented. We appreciate your patience.")
460
- process.exit()
461
- }
462
- console.log(colors.Blue, `Episode ${anime.episode_number+1} of ${anime.anime_id.replaceAll("-", " ")} found.\n`)
463
- if (link.includes("animixplay.to") && config.player === "VLC"){
464
- console.log(colors.Red, "Warning; animix.to uses m3u8 playlists. Without custom plugins, VLC will not be able to play this file format. It is recomended to use another player for this show/film.")
465
- }
466
- console.log(colors.Cyan, "1/p) Play")
467
- console.log(colors.Cyan, "2/d) Download")
468
- console.log(colors.Cyan, "3/l) Show Link")
469
- console.log(colors.Cyan, "4/q) quit")
470
- choice = (await selection(4, "select;", ["p", "d", "l", "q"]))
471
- switch (choice){
472
- case "p":
473
- case "1":
474
- await play(link, anime, null)
475
- break
476
- case "d":
477
- case "2":
478
- await download(link, anime.anime_id+(anime.episode_number+1)+".mp4")
479
- break
480
- case "l":
481
- case "3":
482
- console.log(colors.Yellow, "Link: "+link)
483
- console.log(colors.Cyan, "\n1/p) Play")
484
- console.log(colors.Cyan, "2/d) Download")
485
- console.log(colors.Cyan, "3/q) quit")
486
- choice = (await selection(3, "select;", ["p", "d", "q"]))
487
- switch (choice){
488
- case "p":
489
- case "1":
490
- await play(link, anime, null)
491
- break
492
- case "d":
493
- case "2":
494
- await download(link, anime.anime_id+anime.episode_number+".mp4")
495
- break
496
- case "q":
497
- case "3":
498
- await main()
499
- process.exit()
52
+ let continue_anime = new Anime_1.Anime();
53
+ await continue_anime.init(config.most_recent.anime_id, cache_folder);
54
+ await continue_anime.play_head(config.most_recent.episode_number, config, cache_folder);
55
+ await continue_anime.player.quit();
56
+ await main();
57
+ break;
58
+ case 2: // Download
59
+ let code = await (0, download_1.download)(cache_folder, config);
60
+ if (code == 1) {
61
+ console.log(chalk.red("Error downloading episodes"));
500
62
  }
501
- break
502
- case "q":
503
- case "4":
504
- await main()
505
- process.exit()
506
- }
507
- }
508
-
509
-
510
- console.clear()
511
- read_config()
512
- console.log(colors.Blue, "Welcome to Ani-Cli-npm\n")
513
- if (config.most_recent.episode_number !== 0){
514
- console.log(colors.White, `Most recently played: ${config.most_recent.anime_id}, ep${config.most_recent.episode_number+1}`)
515
- }
516
- async function main(){
517
- if (config.player === "VLC"){
518
- console.log(colors.Red, "Warning; if you do not have mpv video player installed, you will have to change your video player to either vlc or browser in config.\n")
519
- }
520
- console.log(colors.Cyan, "\n1/s) Search")
521
- if (config.most_recent.episode_number !== 0){
522
- console.log(colors.Cyan, "2/c) Continue")
523
- }else{
524
- console.log(colors.White, "2/c) Continue")
525
- }
526
- console.log(colors.Cyan, "3/C) Config")
527
- console.log(colors.Cyan, "4/q) Quit")
528
- let choice = await selection(4, "select;", ["s", "c", "C", "q"])
529
- switch (choice){
530
- case "s":
531
- case "1":
532
- await search()
533
- break
534
- case "c":
535
- case "2":
536
- if (config.most_recent.episode_number !== 0){
537
- await _continue()
538
- }else{
539
- console.log(colors.White, "No episodes watched recently")
540
- await main()
541
- }
542
-
543
- break
544
- case "C":
545
- case "3":
63
+ await main();
64
+ break;
65
+ case 3: // Options
546
66
  let temp = structuredClone(config);
547
67
  let exit_code;
548
68
  while (true) {
549
- temp, exit_code = await config_(temp)
69
+ // @ts-ignore
70
+ temp, exit_code = await (0, change_config_1.config_)(temp);
550
71
  if (exit_code === 1) {
551
- config = temp
72
+ config = temp;
552
73
  //proxyAgent = new HttpsProxyAgent(config.proxy);
553
- console.clear()
554
- console.log(colors.Blue, "ANI-CLI-NPM \n")
555
- console.log(colors.Yellow, "Config changed.")
556
- break
557
- } else if (exit_code === 2) {
558
- temp = config
559
- console.clear()
560
- console.log(colors.Blue, "ANI-CLI-NPM \n")
561
- console.log(colors.Yellow, "Config changes disregarded.")
562
- break
74
+ console.clear();
75
+ console.log(chalk.yellow("Config changed."));
76
+ break;
77
+ }
78
+ else if (exit_code === 2) {
79
+ temp = config;
80
+ console.clear();
81
+ console.log(chalk.yellow("Config changes disregarded."));
82
+ break;
563
83
  }
564
84
  }
565
- try{
566
- fs.writeFileSync(getAppDataPath()+"/ani-cli-npm.conf", JSON.stringify(config))
567
- }catch{
568
- console.log(colors.Red, "Error writing to .conf file.")
85
+ try {
86
+ fs_1.default.writeFileSync(cache_folder + "/config.conf", JSON.stringify(config));
87
+ }
88
+ catch {
89
+ console.log(chalk.red("Error writing to .conf file."));
569
90
  }
570
- await main()
571
- break
572
- case "q":
573
- case "4":
574
- console.log(colors.Black, "Exiting...")
575
- process.exit()
91
+ await main();
92
+ break;
93
+ case 4: // Quit
94
+ console.log("Exit");
576
95
  }
96
+ // await search()
577
97
  }
578
-
579
-
580
- main()
98
+ main();