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/dist/storage.js
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
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.loadSettings = loadSettings;
|
|
7
|
+
exports.saveSettings = saveSettings;
|
|
8
|
+
exports.getProviderBaseUrl = getProviderBaseUrl;
|
|
9
|
+
exports.loadHistory = loadHistory;
|
|
10
|
+
exports.saveHistoryEntry = saveHistoryEntry;
|
|
11
|
+
exports.clearHistory = clearHistory;
|
|
12
|
+
exports.loadAuthSession = loadAuthSession;
|
|
13
|
+
exports.saveAuthSession = saveAuthSession;
|
|
14
|
+
exports.clearAuthSession = clearAuthSession;
|
|
15
|
+
exports.getProviderCookieHeader = getProviderCookieHeader;
|
|
16
|
+
exports.getProviderToken = getProviderToken;
|
|
17
|
+
const fs_1 = __importDefault(require("fs"));
|
|
18
|
+
const path_1 = __importDefault(require("path"));
|
|
19
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
20
|
+
const os_1 = __importDefault(require("os"));
|
|
21
|
+
const OLD_DATA_DIR = path_1.default.join(__dirname, '.data');
|
|
22
|
+
const DATA_DIR = path_1.default.join(os_1.default.homedir(), '.nekostream-cli');
|
|
23
|
+
const HISTORY_FILE = path_1.default.join(DATA_DIR, 'history.json');
|
|
24
|
+
const SETTINGS_FILE = path_1.default.join(DATA_DIR, 'settings.json');
|
|
25
|
+
const AUTH_SESSIONS_FILE = path_1.default.join(DATA_DIR, 'auth-sessions.json');
|
|
26
|
+
// Ensure directory exists
|
|
27
|
+
if (!fs_1.default.existsSync(DATA_DIR)) {
|
|
28
|
+
fs_1.default.mkdirSync(DATA_DIR, { recursive: true });
|
|
29
|
+
// Migrate data from old directory if it exists
|
|
30
|
+
if (fs_1.default.existsSync(OLD_DATA_DIR)) {
|
|
31
|
+
try {
|
|
32
|
+
fs_1.default.cpSync(OLD_DATA_DIR, DATA_DIR, { recursive: true });
|
|
33
|
+
}
|
|
34
|
+
catch (e) { }
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const DEFAULT_SETTINGS = {
|
|
38
|
+
defaultProvider: 'animevietsub',
|
|
39
|
+
defaultQuality: '1080p',
|
|
40
|
+
autoPlayNext: false,
|
|
41
|
+
developerMode: false,
|
|
42
|
+
providerDomains: {},
|
|
43
|
+
discordRpcEnabled: true
|
|
44
|
+
};
|
|
45
|
+
// ── Settings ─────────────────────────────────────────────────────────────────
|
|
46
|
+
function loadSettings() {
|
|
47
|
+
try {
|
|
48
|
+
if (fs_1.default.existsSync(SETTINGS_FILE)) {
|
|
49
|
+
const data = fs_1.default.readFileSync(SETTINGS_FILE, 'utf-8');
|
|
50
|
+
return { ...DEFAULT_SETTINGS, ...JSON.parse(data) };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
console.warn('Failed to load settings', e);
|
|
55
|
+
}
|
|
56
|
+
return DEFAULT_SETTINGS;
|
|
57
|
+
}
|
|
58
|
+
function saveSettings(settings) {
|
|
59
|
+
const current = loadSettings();
|
|
60
|
+
const updated = { ...current, ...settings };
|
|
61
|
+
fs_1.default.writeFileSync(SETTINGS_FILE, JSON.stringify(updated, null, 2), 'utf-8');
|
|
62
|
+
}
|
|
63
|
+
const PROVIDER_DEFAULT_DOMAINS = {
|
|
64
|
+
animevietsub: 'https://animevietsub.site',
|
|
65
|
+
anime47: 'https://anime47.best',
|
|
66
|
+
animehay: 'https://animehay.ink'
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Returns the base URL for a provider, respecting custom domain settings.
|
|
70
|
+
* Mirrors the domain injection logic in providers.ts getProvider().
|
|
71
|
+
*/
|
|
72
|
+
function getProviderBaseUrl(providerName) {
|
|
73
|
+
const settings = loadSettings();
|
|
74
|
+
const custom = settings.providerDomains?.[providerName];
|
|
75
|
+
if (custom && custom.trim()) {
|
|
76
|
+
const d = custom.trim();
|
|
77
|
+
return (d.startsWith('http') ? d : 'https://' + d).replace(/\/$/, '');
|
|
78
|
+
}
|
|
79
|
+
return PROVIDER_DEFAULT_DOMAINS[providerName] ?? `https://${providerName}.com`;
|
|
80
|
+
}
|
|
81
|
+
// ── History ──────────────────────────────────────────────────────────────────
|
|
82
|
+
function loadHistory() {
|
|
83
|
+
try {
|
|
84
|
+
if (fs_1.default.existsSync(HISTORY_FILE)) {
|
|
85
|
+
const data = fs_1.default.readFileSync(HISTORY_FILE, 'utf-8');
|
|
86
|
+
return JSON.parse(data);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch (e) {
|
|
90
|
+
console.warn('Failed to load history', e);
|
|
91
|
+
}
|
|
92
|
+
return [];
|
|
93
|
+
}
|
|
94
|
+
function saveHistoryEntry(entry) {
|
|
95
|
+
const history = loadHistory();
|
|
96
|
+
const newEntry = { ...entry, timestamp: Date.now() };
|
|
97
|
+
// Remove duplicate entry for the same anime
|
|
98
|
+
const filtered = history.filter(h => h.animeId !== entry.animeId || h.provider !== entry.provider);
|
|
99
|
+
filtered.unshift(newEntry); // Add to top
|
|
100
|
+
// Keep last 100 entries
|
|
101
|
+
if (filtered.length > 100)
|
|
102
|
+
filtered.pop();
|
|
103
|
+
fs_1.default.writeFileSync(HISTORY_FILE, JSON.stringify(filtered, null, 2), 'utf-8');
|
|
104
|
+
}
|
|
105
|
+
function clearHistory() {
|
|
106
|
+
if (fs_1.default.existsSync(HISTORY_FILE)) {
|
|
107
|
+
fs_1.default.unlinkSync(HISTORY_FILE);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Derive a stable 32-byte encryption key from machine identity.
|
|
112
|
+
* Not cryptographically robust against local admin access,
|
|
113
|
+
* but sufficient to prevent casual reading of the JSON file.
|
|
114
|
+
*/
|
|
115
|
+
function deriveEncryptionKey() {
|
|
116
|
+
const seed = `nekostream-cli:${os_1.default.hostname()}:${os_1.default.userInfo().username}:auth-v1`;
|
|
117
|
+
return crypto_1.default.createHash('sha256').update(seed).digest();
|
|
118
|
+
}
|
|
119
|
+
function encryptPayload(raw) {
|
|
120
|
+
try {
|
|
121
|
+
const key = deriveEncryptionKey();
|
|
122
|
+
const iv = crypto_1.default.randomBytes(16);
|
|
123
|
+
const cipher = crypto_1.default.createCipheriv('aes-256-cbc', key, iv);
|
|
124
|
+
const encrypted = Buffer.concat([cipher.update(raw, 'utf8'), cipher.final()]);
|
|
125
|
+
return iv.toString('hex') + ':' + encrypted.toString('base64');
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// Fallback: plain base64 if crypto fails
|
|
129
|
+
return 'plain:' + Buffer.from(raw, 'utf8').toString('base64');
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function decryptPayload(encoded) {
|
|
133
|
+
try {
|
|
134
|
+
if (encoded.startsWith('plain:')) {
|
|
135
|
+
return Buffer.from(encoded.slice(6), 'base64').toString('utf8');
|
|
136
|
+
}
|
|
137
|
+
const [ivHex, encryptedB64] = encoded.split(':');
|
|
138
|
+
if (!ivHex || !encryptedB64)
|
|
139
|
+
return null;
|
|
140
|
+
const key = deriveEncryptionKey();
|
|
141
|
+
const iv = Buffer.from(ivHex, 'hex');
|
|
142
|
+
const encrypted = Buffer.from(encryptedB64, 'base64');
|
|
143
|
+
const decipher = crypto_1.default.createDecipheriv('aes-256-cbc', key, iv);
|
|
144
|
+
return Buffer.concat([decipher.update(encrypted), decipher.final()]).toString('utf8');
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function loadAuthSessionsRaw() {
|
|
151
|
+
try {
|
|
152
|
+
if (fs_1.default.existsSync(AUTH_SESSIONS_FILE)) {
|
|
153
|
+
const raw = fs_1.default.readFileSync(AUTH_SESSIONS_FILE, 'utf-8');
|
|
154
|
+
const parsed = JSON.parse(raw);
|
|
155
|
+
const result = {};
|
|
156
|
+
for (const [provider, encoded] of Object.entries(parsed)) {
|
|
157
|
+
const decrypted = decryptPayload(encoded);
|
|
158
|
+
if (!decrypted)
|
|
159
|
+
continue;
|
|
160
|
+
result[provider] = JSON.parse(decrypted);
|
|
161
|
+
}
|
|
162
|
+
return result;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
catch (e) {
|
|
166
|
+
console.warn('[Auth] Failed to load auth sessions:', e);
|
|
167
|
+
}
|
|
168
|
+
return {};
|
|
169
|
+
}
|
|
170
|
+
function saveAuthSessionsRaw(sessions) {
|
|
171
|
+
const encoded = {};
|
|
172
|
+
for (const [provider, session] of Object.entries(sessions)) {
|
|
173
|
+
encoded[provider] = encryptPayload(JSON.stringify(session));
|
|
174
|
+
}
|
|
175
|
+
fs_1.default.writeFileSync(AUTH_SESSIONS_FILE, JSON.stringify(encoded, null, 2), 'utf-8');
|
|
176
|
+
}
|
|
177
|
+
function loadAuthSession(provider) {
|
|
178
|
+
const sessions = loadAuthSessionsRaw();
|
|
179
|
+
return sessions[provider] ?? null;
|
|
180
|
+
}
|
|
181
|
+
function saveAuthSession(provider, session) {
|
|
182
|
+
const sessions = loadAuthSessionsRaw();
|
|
183
|
+
sessions[provider] = { ...session, provider };
|
|
184
|
+
saveAuthSessionsRaw(sessions);
|
|
185
|
+
}
|
|
186
|
+
function clearAuthSession(provider) {
|
|
187
|
+
const sessions = loadAuthSessionsRaw();
|
|
188
|
+
delete sessions[provider];
|
|
189
|
+
saveAuthSessionsRaw(sessions);
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Build Cookie header string from stored session.
|
|
193
|
+
* Filters out expired cookies.
|
|
194
|
+
*/
|
|
195
|
+
function getProviderCookieHeader(provider) {
|
|
196
|
+
const session = loadAuthSession(provider);
|
|
197
|
+
if (!session)
|
|
198
|
+
return null;
|
|
199
|
+
const nowEpoch = Date.now() / 1000;
|
|
200
|
+
const validCookies = session.cookies.filter(cookie => {
|
|
201
|
+
if (!cookie.expirationDate)
|
|
202
|
+
return true;
|
|
203
|
+
return cookie.expirationDate > nowEpoch;
|
|
204
|
+
});
|
|
205
|
+
if (validCookies.length === 0)
|
|
206
|
+
return null;
|
|
207
|
+
return validCookies.map(c => `${c.name}=${c.value}`).join('; ');
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Returns stored JWT access token for providers that use Bearer auth (e.g. Anime47).
|
|
211
|
+
*/
|
|
212
|
+
function getProviderToken(provider) {
|
|
213
|
+
const session = loadAuthSession(provider);
|
|
214
|
+
return session?.accessToken ?? null;
|
|
215
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
const auth_service_1 = require("./scrapers/auth-service");
|
|
40
|
+
const fs_1 = __importDefault(require("fs"));
|
|
41
|
+
const cheerio = __importStar(require("cheerio"));
|
|
42
|
+
(async () => {
|
|
43
|
+
const watchUrl = 'https://anime47.best/xem/android-wa-keiken-ninzuu-ni-hairimasu-ka-10264/ep-1-117377';
|
|
44
|
+
console.log('Fetching watchUrl...', watchUrl);
|
|
45
|
+
const watchHtml = await (0, auth_service_1.fetchA47Page)(watchUrl) || '';
|
|
46
|
+
fs_1.default.writeFileSync('watch.html', watchHtml);
|
|
47
|
+
const $ = cheerio.load(watchHtml);
|
|
48
|
+
// Find all scripts that might contain state
|
|
49
|
+
$('script').each((i, el) => {
|
|
50
|
+
const text = $(el).html() || '';
|
|
51
|
+
if (text.includes('window.') || text.includes('episodes') || text.includes('server')) {
|
|
52
|
+
console.log(`Script ${i} length: ${text.length}`);
|
|
53
|
+
if (text.length < 5000)
|
|
54
|
+
console.log(text.substring(0, 500));
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
// Let's also check if there are buttons with "Tập"
|
|
58
|
+
let epCount = 0;
|
|
59
|
+
$('*').each((i, el) => {
|
|
60
|
+
const text = $(el).text().trim();
|
|
61
|
+
if (text.startsWith('Tập ') || /^\d{1,3}$/.test(text)) {
|
|
62
|
+
if ($(el).attr('class') || $(el).attr('id')) {
|
|
63
|
+
// console.log('Potential episode element:', el.tagName, $(el).attr('class'), text);
|
|
64
|
+
epCount++;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
console.log('Found elements with short numbers or "Tap":', epCount);
|
|
69
|
+
})();
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
const auth_service_1 = require("./scrapers/auth-service");
|
|
40
|
+
const cheerio = __importStar(require("cheerio"));
|
|
41
|
+
const fs_1 = __importDefault(require("fs"));
|
|
42
|
+
(async () => {
|
|
43
|
+
const watchUrl = 'https://anime47.best/xem/android-wa-keiken-ninzuu-ni-hairimasu-ka-10264/ep-1-117377';
|
|
44
|
+
console.log('Fetching watchUrl...', watchUrl);
|
|
45
|
+
const watchHtml = await (0, auth_service_1.fetchA47Page)(watchUrl) || '';
|
|
46
|
+
const $w = cheerio.load(watchHtml);
|
|
47
|
+
const links = [];
|
|
48
|
+
$w('a').each((_, el) => {
|
|
49
|
+
const href = $w(el).attr('href');
|
|
50
|
+
if (href && (href.includes('ep-') || href.includes('tap-') || href.includes('episode'))) {
|
|
51
|
+
links.push({ text: $w(el).text().trim(), href });
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
console.log('Found potential episode links:', links);
|
|
55
|
+
fs_1.default.writeFileSync('watch.html', watchHtml);
|
|
56
|
+
})();
|
package/dist/test-a47.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const auth_service_ts_1 = require("./scrapers/auth-service.ts");
|
|
4
|
+
(async () => {
|
|
5
|
+
const html = await (0, auth_service_ts_1.fetchA47Page)('https://anime47.love/api/anime/10208/episodes?lang=vi');
|
|
6
|
+
console.log(html);
|
|
7
|
+
process.exit(0);
|
|
8
|
+
})();
|
package/dist/ui.js
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.clearScreen = clearScreen;
|
|
40
|
+
exports.printBanner = printBanner;
|
|
41
|
+
exports.drawAnimeCard = drawAnimeCard;
|
|
42
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
43
|
+
const storage_1 = require("./storage");
|
|
44
|
+
const fs = __importStar(require("fs"));
|
|
45
|
+
const path = __importStar(require("path"));
|
|
46
|
+
function clearScreen() {
|
|
47
|
+
try {
|
|
48
|
+
const settings = (0, storage_1.loadSettings)();
|
|
49
|
+
if (settings.developerMode)
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
catch (e) { } // Fallback in case storage isn't ready yet
|
|
53
|
+
// \x1B[2J clears visible screen, \x1B[3J clears scrollback buffer, \x1B[H moves cursor to top left
|
|
54
|
+
process.stdout.write('\x1B[2J\x1B[3J\x1B[H');
|
|
55
|
+
if (process.stdin.isTTY) {
|
|
56
|
+
try {
|
|
57
|
+
// Force Node.js to re-apply the OS console mode API (fixes Playwright/Chromium corrupting the console on Windows)
|
|
58
|
+
process.stdin.setRawMode(false);
|
|
59
|
+
process.stdin.setRawMode(true);
|
|
60
|
+
}
|
|
61
|
+
catch (e) { }
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function interpolateColor(c1, c2, factor) {
|
|
65
|
+
const result = c1.slice();
|
|
66
|
+
for (let i = 0; i < 3; i++) {
|
|
67
|
+
result[i] = Math.round(result[i] + factor * (c2[i] - c1[i]));
|
|
68
|
+
}
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
function hexToRgb(hex) {
|
|
72
|
+
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
73
|
+
return result ? [parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)] : [0, 0, 0];
|
|
74
|
+
}
|
|
75
|
+
function applyGradient(text, startHex, endHex) {
|
|
76
|
+
const c1 = hexToRgb(startHex);
|
|
77
|
+
const c2 = hexToRgb(endHex);
|
|
78
|
+
let output = '';
|
|
79
|
+
for (let i = 0; i < text.length; i++) {
|
|
80
|
+
const factor = i / Math.max(text.length - 1, 1);
|
|
81
|
+
const color = interpolateColor(c1, c2, factor);
|
|
82
|
+
output += chalk_1.default.rgb(color[0], color[1], color[2])(text[i]);
|
|
83
|
+
}
|
|
84
|
+
return output;
|
|
85
|
+
}
|
|
86
|
+
function printBanner(title, subtitle) {
|
|
87
|
+
console.log('\n');
|
|
88
|
+
let version = 'unknown';
|
|
89
|
+
try {
|
|
90
|
+
const isCompiled = __dirname.endsWith('dist') || __dirname.endsWith('dist\\') || __dirname.endsWith('dist/');
|
|
91
|
+
const basePath = isCompiled ? path.join(__dirname, '..') : __dirname;
|
|
92
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(basePath, 'package.json'), 'utf-8'));
|
|
93
|
+
version = pkg.version;
|
|
94
|
+
}
|
|
95
|
+
catch (e) { }
|
|
96
|
+
const bracket = [
|
|
97
|
+
" ▝▜▄ ",
|
|
98
|
+
" ▝▜▄ ",
|
|
99
|
+
" ▗▟▀ ",
|
|
100
|
+
" ▝▀ "
|
|
101
|
+
];
|
|
102
|
+
const color1 = hexToRgb('#4285F4');
|
|
103
|
+
const color2 = hexToRgb('#DB2777');
|
|
104
|
+
const coloredBracket = bracket.map((line, i) => {
|
|
105
|
+
const factor = i / 3;
|
|
106
|
+
const color = interpolateColor(color1, color2, factor);
|
|
107
|
+
return chalk_1.default.rgb(color[0], color[1], color[2]).bold(line);
|
|
108
|
+
});
|
|
109
|
+
console.log(`${coloredBracket[0]} ${chalk_1.default.bold.white(`NekoStream CLI v${version}`)}`);
|
|
110
|
+
console.log(`${coloredBracket[1]}`);
|
|
111
|
+
console.log(`${coloredBracket[2]} ${chalk_1.default.cyan(title ? title : '')}`);
|
|
112
|
+
if (subtitle) {
|
|
113
|
+
console.log(`${coloredBracket[3]} ${chalk_1.default.gray(subtitle)}`);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
console.log(`${coloredBracket[3]}`);
|
|
117
|
+
}
|
|
118
|
+
console.log('');
|
|
119
|
+
}
|
|
120
|
+
function truncate(str, maxLength) {
|
|
121
|
+
if (str.length <= maxLength)
|
|
122
|
+
return str;
|
|
123
|
+
return str.slice(0, maxLength - 3) + '...';
|
|
124
|
+
}
|
|
125
|
+
function wrapText(text, maxWidth) {
|
|
126
|
+
const words = text.split(' ');
|
|
127
|
+
const lines = [];
|
|
128
|
+
let currentLine = '';
|
|
129
|
+
for (const word of words) {
|
|
130
|
+
if ((currentLine + word).length > maxWidth) {
|
|
131
|
+
if (currentLine)
|
|
132
|
+
lines.push(currentLine.trim());
|
|
133
|
+
currentLine = word + ' ';
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
currentLine += word + ' ';
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (currentLine)
|
|
140
|
+
lines.push(currentLine.trim());
|
|
141
|
+
return lines;
|
|
142
|
+
}
|
|
143
|
+
function drawAnimeCard(anime) {
|
|
144
|
+
const width = 76;
|
|
145
|
+
const pad = (str, len) => {
|
|
146
|
+
const visibleLength = str.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, '').length;
|
|
147
|
+
if (visibleLength >= len)
|
|
148
|
+
return str;
|
|
149
|
+
return str + ' '.repeat(len - visibleLength);
|
|
150
|
+
};
|
|
151
|
+
// Use a gradient for the border? Or just magenta/cyan. Let's use a solid color from the theme.
|
|
152
|
+
const borderColor = chalk_1.default.hex('#9b59b6'); // Purple border
|
|
153
|
+
const titleColor = chalk_1.default.hex('#FF69B4').bold; // Pink title
|
|
154
|
+
const labelColor = chalk_1.default.hex('#00bcd4'); // Cyan labels
|
|
155
|
+
const valueColor = chalk_1.default.white;
|
|
156
|
+
console.log(borderColor(' ╭' + '─'.repeat(width) + '╮'));
|
|
157
|
+
const title = truncate(anime.title, width - 4);
|
|
158
|
+
console.log(borderColor(' │ ') + pad(titleColor(title), width - 2) + borderColor(' │'));
|
|
159
|
+
if (anime.titleAlt) {
|
|
160
|
+
console.log(borderColor(' │ ') + pad(chalk_1.default.gray(truncate(anime.titleAlt, width - 4)), width - 2) + borderColor(' │'));
|
|
161
|
+
}
|
|
162
|
+
console.log(borderColor(' ├' + '─'.repeat(width) + '┤'));
|
|
163
|
+
const row1 = [];
|
|
164
|
+
row1.push(labelColor(`Số tập: `) + valueColor(anime.episodeCount || '??'));
|
|
165
|
+
row1.push(labelColor(`Năm SX: `) + valueColor(anime.year && anime.year > 1900 ? anime.year : '??'));
|
|
166
|
+
const genresStr = anime.genres && anime.genres.length > 0 ? anime.genres.slice(0, 2).join(', ') : '??';
|
|
167
|
+
row1.push(chalk_1.default.hex('#2980b9')(`Thể loại: `) + valueColor(genresStr));
|
|
168
|
+
console.log(borderColor(' │ ') + pad(row1.join(chalk_1.default.gray(' | ')), width - 2) + borderColor(' │'));
|
|
169
|
+
const row2 = [];
|
|
170
|
+
row2.push(labelColor(`Đạo diễn: `) + valueColor(anime.director || '??'));
|
|
171
|
+
row2.push(labelColor(`Studio: `) + valueColor(anime.studio || '??'));
|
|
172
|
+
row2.push(labelColor(`Season: `) + valueColor(anime.season || '??'));
|
|
173
|
+
console.log(borderColor(' │ ') + pad(row2.join(chalk_1.default.gray(' | ')), width - 2) + borderColor(' │'));
|
|
174
|
+
if (anime.description) {
|
|
175
|
+
console.log(borderColor(' │ ') + pad('', width - 2) + borderColor(' │'));
|
|
176
|
+
// Clean HTML from description
|
|
177
|
+
const cleanDesc = anime.description.replace(/<[^>]*>?/gm, '');
|
|
178
|
+
const descLines = wrapText(cleanDesc, width - 4);
|
|
179
|
+
for (let i = 0; i < Math.min(descLines.length, 6); i++) {
|
|
180
|
+
let line = descLines[i];
|
|
181
|
+
if (i === 5 && descLines.length > 6)
|
|
182
|
+
line = truncate(line, width - 7) + '...';
|
|
183
|
+
console.log(borderColor(' │ ') + pad(chalk_1.default.gray(line), width - 2) + borderColor(' │'));
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
console.log(borderColor(' ╰' + '─'.repeat(width) + '╯\n'));
|
|
187
|
+
}
|
package/isolate.css
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
html, body {
|
|
2
|
+
width: 100% !important;
|
|
3
|
+
height: 100% !important;
|
|
4
|
+
margin: 0 !important;
|
|
5
|
+
padding: 0 !important;
|
|
6
|
+
overflow: hidden !important;
|
|
7
|
+
background: #000 !important;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
#app, #root, #q-app, main, .main, .watch-page {
|
|
11
|
+
width: 100% !important;
|
|
12
|
+
height: 100% !important;
|
|
13
|
+
max-width: 100% !important;
|
|
14
|
+
margin: 0 !important;
|
|
15
|
+
padding: 0 !important;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.jwplayer, .jw-player-wrapper, .jw-holder, .jw-media, .jw-video,
|
|
19
|
+
.art-video-player, .video-js, .vjs-tech, video,
|
|
20
|
+
iframe[src*="vlogphim"], iframe[src*="anime47"], iframe[src*="nonprofit"], iframe[src*="anime3s"] {
|
|
21
|
+
width: 100% !important;
|
|
22
|
+
height: 100% !important;
|
|
23
|
+
max-width: 100% !important;
|
|
24
|
+
max-height: 100% !important;
|
|
25
|
+
margin: 0 !important;
|
|
26
|
+
padding: 0 !important;
|
|
27
|
+
padding-bottom: 0 !important;
|
|
28
|
+
padding-top: 0 !important;
|
|
29
|
+
top: 0 !important;
|
|
30
|
+
left: 0 !important;
|
|
31
|
+
transform: none !important;
|
|
32
|
+
background: #000 !important;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.player-section, .video-container, .watch-player {
|
|
36
|
+
width: 100% !important;
|
|
37
|
+
height: 100% !important;
|
|
38
|
+
max-width: 100% !important;
|
|
39
|
+
max-height: 100% !important;
|
|
40
|
+
margin: 0 !important;
|
|
41
|
+
padding: 0 !important;
|
|
42
|
+
padding-bottom: 0 !important;
|
|
43
|
+
padding-top: 0 !important;
|
|
44
|
+
top: 0 !important;
|
|
45
|
+
left: 0 !important;
|
|
46
|
+
transform: none !important;
|
|
47
|
+
overflow: hidden !important;
|
|
48
|
+
background: #000 !important;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.player-wrapper {
|
|
52
|
+
width: 100% !important;
|
|
53
|
+
height: 100% !important;
|
|
54
|
+
max-width: 100% !important;
|
|
55
|
+
max-height: 100% !important;
|
|
56
|
+
margin: 0 !important;
|
|
57
|
+
padding: 0 !important;
|
|
58
|
+
padding-bottom: 0 !important;
|
|
59
|
+
padding-top: 0 !important;
|
|
60
|
+
top: 0 !important;
|
|
61
|
+
left: 0 !important;
|
|
62
|
+
transform: none !important;
|
|
63
|
+
background: #000 !important;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.watch-player {
|
|
67
|
+
display: flex !important;
|
|
68
|
+
flex-direction: column !important;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.watch-player > .player-wrapper,
|
|
72
|
+
.watch-player > .player-wrapper.jw-active,
|
|
73
|
+
.watch-player > .jw-player-wrapper,
|
|
74
|
+
.watch-player > .jwplayer,
|
|
75
|
+
.watch-player .player-wrapper.jw-active {
|
|
76
|
+
flex: 1 1 auto !important;
|
|
77
|
+
min-height: 0 !important;
|
|
78
|
+
height: auto !important;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
div[role="dialog"], div[role="alertdialog"], .modal, .popup, .overlay-ads,
|
|
82
|
+
[class*="ad-"], [id*="ad-"] {
|
|
83
|
+
display: none !important;
|
|
84
|
+
visibility: hidden !important;
|
|
85
|
+
opacity: 0 !important;
|
|
86
|
+
pointer-events: none !important;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
header, nav, .header, #header, .navbar, .nav, #nav, .menu, #menu, .top-bar, .head,
|
|
90
|
+
.q-header, .q-footer, .q-drawer,
|
|
91
|
+
.ads, .ad, .banner, .overlay-ads, [class*="ad-"], [id*="ad-"],
|
|
92
|
+
.watch-player .player-controls, .watch-player .server-section, .watch-player .functional-section,
|
|
93
|
+
.player-controls, .server-section, .functional-section, .watch-player > .q-card:not(:first-child) {
|
|
94
|
+
display: none !important;
|
|
95
|
+
visibility: hidden !important;
|
|
96
|
+
opacity: 0 !important;
|
|
97
|
+
pointer-events: none !important;
|
|
98
|
+
height: 0 !important;
|
|
99
|
+
width: 0 !important;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.jw-overlays,
|
|
103
|
+
.jw-display,
|
|
104
|
+
.jw-controls,
|
|
105
|
+
.jw-display-icon-container,
|
|
106
|
+
.jw-icon-display,
|
|
107
|
+
video {
|
|
108
|
+
pointer-events: auto !important;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.jw-wrapper,
|
|
112
|
+
.jw-media,
|
|
113
|
+
.jwplayer {
|
|
114
|
+
background: #000 !important;
|
|
115
|
+
}
|