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 +30 -0
- package/bin/nekostream.js +4 -0
- package/dist/discord.js +144 -0
- package/dist/fetch-avs.js +22 -0
- package/dist/index.js +808 -0
- package/dist/player.js +37 -0
- package/dist/prompts-wrapper.js +81 -0
- package/dist/providers.js +35 -0
- package/dist/scrapers/anilist-api.js +516 -0
- package/dist/scrapers/anilist.js +82 -0
- package/dist/scrapers/auth-service.js +1245 -0
- package/dist/scrapers/base.js +41 -0
- package/dist/scrapers/crawler.js +301 -0
- package/dist/scrapers/external-api.js +68 -0
- package/dist/scrapers/fingerprint.js +240 -0
- package/dist/scrapers/interceptor.js +288 -0
- package/dist/scrapers/jikan-api.js +60 -0
- package/dist/scrapers/providers/anime47.js +941 -0
- package/dist/scrapers/providers/animehay.js +640 -0
- package/dist/scrapers/providers/animevietsub.js +1455 -0
- package/dist/storage.js +215 -0
- package/dist/test-a47-eps.js +69 -0
- package/dist/test-a47-real.js +56 -0
- package/dist/test-a47.js +8 -0
- package/dist/ui.js +187 -0
- package/isolate.css +115 -0
- package/isolate.js +178 -0
- package/package.json +60 -0
- package/player-main.js +225 -0
- package/player.html +376 -0
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!
|
package/dist/discord.js
ADDED
|
@@ -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
|
+
})();
|