nekostream 2026.0.1

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/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # NekoStream CLI
2
+
3
+ NekoStream CLI là một ứng dụng dòng lệnh xem anime tốc độ cao, siêu nhẹ, tích hợp trình phát video đa tính năng và hỗ trợ lấy dữ liệu từ nhiều nguồn (providers) phổ biến tại Việt Nam.
4
+
5
+ ## Cài đặt & Sử dụng
6
+
7
+ Yêu cầu: [Node.js](https://nodejs.org/) (phiên bản 18+).
8
+
9
+ Mở terminal và chạy lệnh cài đặt toàn cục:
10
+ ```bash
11
+ npm install -g nekostream-cli
12
+ ```
13
+
14
+ Sau khi cài đặt xong, bạn có thể gọi ứng dụng từ bất kỳ đâu:
15
+ ```bash
16
+ nekostream
17
+ ```
18
+
19
+
20
+ ## Tính năng chính
21
+ - Tốc độ cao, gọn nhẹ, điều khiển hoàn toàn bằng phím tắt.
22
+ - Theo dõi lịch sử xem phim và tính năng "Tiếp tục xem".
23
+ - Thay đổi tùy chọn Domain của Provider linh hoạt.
24
+ - Trình phát (Player) tự động phát tập tiếp theo.
25
+
26
+ ## Tuyên bố miễn trừ trách nhiệm
27
+ - **Phi lợi nhuận**: Dự án không nhằm mục đích thương mại hóa, được tạo ra hoàn toàn vì cộng đồng.
28
+ - **Không lưu trữ**: NekoStream CLI đóng vai trò là cầu nối (client). Chúng tôi không lưu trữ, không phát tán bất cứ thông tin hay nội dung số nào ngoài mã nguồn trên thư mục Github này.
29
+ - **Tính riêng tư**: Dự án KHÔNG thu thập thông tin của bạn, không sử dụng bất kỳ máy chủ trung gian nào. Mọi request (kết nối) đều là kết nối trực tiếp từ máy tính của bạn đến máy chủ Provider.
30
+ - **Mã nguồn mở**: Dự án có sử dụng AI trong quá trình phát triển. Bạn có thể thoải mái clone và tham khảo code, nhưng vui lòng để lại credit. Cảm ơn!
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+
3
+ // NekoStream CLI Entry Point
4
+ require('../dist/index.js');
@@ -0,0 +1,144 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.initDiscord = initDiscord;
7
+ exports.setBrowsingPresence = setBrowsingPresence;
8
+ exports.setWatchingPresence = setWatchingPresence;
9
+ exports.clearDiscordPresence = clearDiscordPresence;
10
+ exports.toggleDiscordPresence = toggleDiscordPresence;
11
+ const discord_rpc_1 = __importDefault(require("discord-rpc"));
12
+ const storage_1 = require("./storage");
13
+ const CLIENT_ID = '1490936797541437440'; // NekoStream Discord App
14
+ let rpcClient = null;
15
+ let startTimestamp = null;
16
+ let currentDetails;
17
+ let currentProvider;
18
+ let currentFeature;
19
+ let currentAnime;
20
+ let currentEpisode;
21
+ let isWatching = false;
22
+ const COMMON_BUTTONS = [
23
+ { label: 'NekoStreamCLI', url: 'https://www.npmjs.com/package/nekostream-cli' },
24
+ { label: 'Tham gia Discord', url: 'https://discord.gg/Y2kq2y26pZ' }
25
+ ];
26
+ const imageCache = {};
27
+ async function fetchAnimeImage(title) {
28
+ const cleanTitle = title.replace(/(vietsub|thuyết minh|tập.*|phần.*)/gi, '').trim();
29
+ if (imageCache[cleanTitle])
30
+ return imageCache[cleanTitle];
31
+ try {
32
+ const res = await fetch(`https://api.jikan.moe/v4/anime?q=${encodeURIComponent(cleanTitle)}&limit=1`, { signal: AbortSignal.timeout(3000) });
33
+ if (res.ok) {
34
+ const data = await res.json();
35
+ if (data.data && data.data.length > 0) {
36
+ const imageUrl = data.data[0].images?.jpg?.large_image_url || data.data[0].images?.jpg?.image_url;
37
+ if (imageUrl) {
38
+ imageCache[cleanTitle] = imageUrl;
39
+ return imageUrl;
40
+ }
41
+ }
42
+ }
43
+ }
44
+ catch (e) { }
45
+ return 'https://cdnjs.cloudflare.com/ajax/libs/twemoji/15.1.0/72x72/1f4fa.png'; // Fallback
46
+ }
47
+ async function initDiscord() {
48
+ const settings = (0, storage_1.loadSettings)();
49
+ if (settings.discordRpcEnabled === false)
50
+ return;
51
+ if (rpcClient)
52
+ return; // Already initialized
53
+ try {
54
+ discord_rpc_1.default.register(CLIENT_ID);
55
+ rpcClient = new discord_rpc_1.default.Client({ transport: 'ipc' });
56
+ rpcClient.on('ready', () => {
57
+ // Restore presence if it was set before ready
58
+ refreshPresence();
59
+ });
60
+ // Catch errors silently to avoid crashing CLI if Discord is closed
61
+ rpcClient.login({ clientId: CLIENT_ID }).catch(() => { });
62
+ }
63
+ catch (e) {
64
+ // Ignore RPC initialization errors
65
+ }
66
+ }
67
+ function refreshPresence() {
68
+ if (isWatching && currentAnime && currentEpisode && currentProvider) {
69
+ setWatchingPresence(currentAnime, currentEpisode, currentProvider);
70
+ }
71
+ else {
72
+ setBrowsingPresence(currentDetails, currentProvider, currentFeature, currentAnime);
73
+ }
74
+ }
75
+ function safeSetActivity(activity) {
76
+ if (!rpcClient)
77
+ return;
78
+ const settings = (0, storage_1.loadSettings)();
79
+ if (settings.discordRpcEnabled === false)
80
+ return;
81
+ try {
82
+ rpcClient.setActivity(activity).catch(() => { });
83
+ }
84
+ catch (e) { }
85
+ }
86
+ async function setBrowsingPresence(details, provider, feature, animeTitle) {
87
+ isWatching = false;
88
+ currentDetails = details;
89
+ currentProvider = provider;
90
+ currentFeature = feature;
91
+ currentAnime = animeTitle;
92
+ currentEpisode = undefined;
93
+ if (!startTimestamp)
94
+ startTimestamp = new Date();
95
+ const stateStr = provider ? `${provider.toUpperCase()}${feature ? ` | ${feature}` : ''}` : (feature || 'NekoStream CLI');
96
+ const largeImageKey = animeTitle ? await fetchAnimeImage(animeTitle) : 'https://cdnjs.cloudflare.com/ajax/libs/twemoji/15.1.0/72x72/1f4fa.png';
97
+ const largeImageText = animeTitle || 'NekoStream';
98
+ safeSetActivity({
99
+ details: details || 'Đang lướt Menu Chính',
100
+ state: stateStr,
101
+ startTimestamp,
102
+ largeImageKey,
103
+ largeImageText,
104
+ instance: false,
105
+ buttons: COMMON_BUTTONS
106
+ });
107
+ }
108
+ async function setWatchingPresence(animeTitle, episodeName, provider) {
109
+ isWatching = true;
110
+ currentAnime = animeTitle;
111
+ currentEpisode = episodeName;
112
+ currentProvider = provider;
113
+ startTimestamp = new Date(); // Reset timer for playback
114
+ const largeImageKey = await fetchAnimeImage(animeTitle);
115
+ safeSetActivity({
116
+ details: `Đang xem: ${animeTitle}`,
117
+ state: `${provider.toUpperCase()} | ${episodeName}`,
118
+ startTimestamp,
119
+ largeImageKey,
120
+ largeImageText: animeTitle,
121
+ smallImageKey: 'https://cdnjs.cloudflare.com/ajax/libs/twemoji/15.1.0/72x72/25b6.png',
122
+ smallImageText: 'Playing',
123
+ instance: false,
124
+ buttons: COMMON_BUTTONS
125
+ });
126
+ }
127
+ function clearDiscordPresence() {
128
+ if (rpcClient) {
129
+ try {
130
+ rpcClient.clearActivity().catch(() => { });
131
+ rpcClient.destroy().catch(() => { });
132
+ }
133
+ catch (e) { }
134
+ rpcClient = null;
135
+ }
136
+ }
137
+ function toggleDiscordPresence(enabled) {
138
+ if (enabled) {
139
+ initDiscord();
140
+ }
141
+ else {
142
+ clearDiscordPresence();
143
+ }
144
+ }
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const storage_1 = require("./storage");
7
+ const playwright_1 = require("playwright");
8
+ const fs_1 = __importDefault(require("fs"));
9
+ (async () => {
10
+ const session = (0, storage_1.loadAuthSession)('animevietsub');
11
+ if (!session)
12
+ return;
13
+ const browser = await playwright_1.chromium.launch();
14
+ const context = await browser.newContext();
15
+ await context.addCookies(session.cookies);
16
+ const page = await context.newPage();
17
+ await page.goto('https://animevietsub.best', { waitUntil: 'domcontentloaded' });
18
+ const html = await page.content();
19
+ fs_1.default.writeFileSync('avs.html', html);
20
+ await browser.close();
21
+ console.log('Saved html');
22
+ })();