blyuecode 0.7.2 → 0.8.3
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/dist/core/deps.js +104 -0
- package/dist/core/image.js +69 -0
- package/dist/core/media.js +184 -0
- package/dist/core/player.js +208 -0
- package/dist/core/videoPlayer.js +44 -0
- package/dist/core/ytdlp.js +211 -0
- package/dist/ui/Chat.js +512 -1
- package/dist/ui/InputBar.js +8 -2
- package/dist/ui/MascotPrompt.js +27 -6
- package/dist/ui/splash.js +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { exec, spawn } from 'child_process';
|
|
2
|
+
import { promisify } from 'util';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import os from 'os';
|
|
6
|
+
const execAsync = promisify(exec);
|
|
7
|
+
const LOG_DIR = path.join(os.homedir(), '.blyue', 'log');
|
|
8
|
+
const LOG_FILE = path.join(LOG_DIR, 'ytdlp.log');
|
|
9
|
+
function log(line) {
|
|
10
|
+
try {
|
|
11
|
+
fs.mkdirSync(LOG_DIR, { recursive: true });
|
|
12
|
+
fs.appendFileSync(LOG_FILE, `${new Date().toISOString()} ${line}\n`, 'utf8');
|
|
13
|
+
}
|
|
14
|
+
catch { }
|
|
15
|
+
}
|
|
16
|
+
export function ytdlpLogPath() {
|
|
17
|
+
return LOG_FILE;
|
|
18
|
+
}
|
|
19
|
+
export function readYtdlpLog(limit = 100) {
|
|
20
|
+
if (!fs.existsSync(LOG_FILE))
|
|
21
|
+
return [];
|
|
22
|
+
try {
|
|
23
|
+
const lines = fs.readFileSync(LOG_FILE, 'utf8').split('\n').filter(Boolean);
|
|
24
|
+
return lines.slice(-limit);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function classify(stderr, exitCode) {
|
|
31
|
+
const s = stderr.toLowerCase();
|
|
32
|
+
const tail = stderr.split('\n').slice(-4).join(' | ').slice(0, 300);
|
|
33
|
+
if (s.includes('temporary failure in name resolution') ||
|
|
34
|
+
s.includes('network is unreachable') ||
|
|
35
|
+
s.includes('connection refused') ||
|
|
36
|
+
s.includes('failed to resolve') ||
|
|
37
|
+
s.includes('no address associated') ||
|
|
38
|
+
s.includes('connection timed out')) {
|
|
39
|
+
return {
|
|
40
|
+
kind: 'network',
|
|
41
|
+
userMessage: 'no network — check your connection and try again',
|
|
42
|
+
detail: tail,
|
|
43
|
+
autoFixable: false,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
if (s.includes('403') || s.includes('forbidden')) {
|
|
47
|
+
return {
|
|
48
|
+
kind: 'forbidden',
|
|
49
|
+
userMessage: 'yt-dlp blocked (HTTP 403) — usually a stale yt-dlp version',
|
|
50
|
+
detail: tail,
|
|
51
|
+
autoFixable: true,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
if (s.includes('429') || s.includes('too many requests')) {
|
|
55
|
+
return {
|
|
56
|
+
kind: 'ratelimit',
|
|
57
|
+
userMessage: 'rate limited by youtube — wait a minute and retry',
|
|
58
|
+
detail: tail,
|
|
59
|
+
autoFixable: false,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
if (s.includes('video unavailable') || s.includes('private video') || s.includes('removed')) {
|
|
63
|
+
return {
|
|
64
|
+
kind: 'notfound',
|
|
65
|
+
userMessage: 'video is unavailable, private, or removed',
|
|
66
|
+
detail: tail,
|
|
67
|
+
autoFixable: false,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
if (s.includes('not available in your country') || s.includes('geo')) {
|
|
71
|
+
return {
|
|
72
|
+
kind: 'geo',
|
|
73
|
+
userMessage: 'video is geo-blocked in your region',
|
|
74
|
+
detail: tail,
|
|
75
|
+
autoFixable: false,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
if (s.includes('no space left')) {
|
|
79
|
+
return {
|
|
80
|
+
kind: 'disk',
|
|
81
|
+
userMessage: 'out of disk space',
|
|
82
|
+
detail: tail,
|
|
83
|
+
autoFixable: false,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
kind: 'unknown',
|
|
88
|
+
userMessage: `yt-dlp failed (exit ${exitCode}): ${tail || 'no output'}`,
|
|
89
|
+
detail: tail,
|
|
90
|
+
autoFixable: false,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
export async function runYtdlp(args, opts = {}) {
|
|
94
|
+
const cmd = 'yt-dlp ' + args.map((a) => `"${a.replace(/"/g, '\\"')}"`).join(' ');
|
|
95
|
+
const started = Date.now();
|
|
96
|
+
try {
|
|
97
|
+
const { stdout, stderr } = await execAsync(cmd, {
|
|
98
|
+
maxBuffer: 20 * 1024 * 1024,
|
|
99
|
+
timeout: opts.timeoutMs ?? 15 * 60 * 1000,
|
|
100
|
+
});
|
|
101
|
+
log(`OK ${Date.now() - started}ms ${cmd.slice(0, 200)}`);
|
|
102
|
+
return { ok: true, stdout, stderr };
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
const stderr = (err.stderr || '') + (err.stdout ? '\n' + err.stdout : '');
|
|
106
|
+
const code = err.code ?? 1;
|
|
107
|
+
const classified = classify(stderr || String(err), code);
|
|
108
|
+
log(`ERR ${Date.now() - started}ms kind=${classified.kind} code=${code} ` +
|
|
109
|
+
`cmd=${cmd.slice(0, 200)} detail=${classified.detail}`);
|
|
110
|
+
return { ok: false, stdout: err.stdout || '', stderr, error: classified };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
export async function runYtdlpStream(args, onProgress, opts = {}) {
|
|
114
|
+
const fullArgs = [...args, '--newline', '--progress'];
|
|
115
|
+
const cmd = 'yt-dlp ' + fullArgs.map((a) => `"${a.replace(/"/g, '\\"')}"`).join(' ');
|
|
116
|
+
const started = Date.now();
|
|
117
|
+
return new Promise((resolve) => {
|
|
118
|
+
const proc = spawn('yt-dlp', fullArgs, { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
119
|
+
let stdout = '';
|
|
120
|
+
let stderr = '';
|
|
121
|
+
const onData = (chunk, toStdout) => {
|
|
122
|
+
const text = chunk.toString('utf8');
|
|
123
|
+
if (toStdout)
|
|
124
|
+
stdout += text;
|
|
125
|
+
else
|
|
126
|
+
stderr += text;
|
|
127
|
+
if (onProgress) {
|
|
128
|
+
for (const line of text.split(/[\r\n]+/)) {
|
|
129
|
+
const t = line.trim();
|
|
130
|
+
if (t)
|
|
131
|
+
onProgress(t);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
proc.stdout?.on('data', (c) => onData(c, true));
|
|
136
|
+
proc.stderr?.on('data', (c) => onData(c, false));
|
|
137
|
+
const timeout = opts.timeoutMs ?? 30 * 60 * 1000;
|
|
138
|
+
const timer = setTimeout(() => {
|
|
139
|
+
try {
|
|
140
|
+
proc.kill('SIGTERM');
|
|
141
|
+
}
|
|
142
|
+
catch { }
|
|
143
|
+
log(`TIMEOUT ${Date.now() - started}ms ${cmd.slice(0, 200)}`);
|
|
144
|
+
resolve({
|
|
145
|
+
ok: false, stdout, stderr,
|
|
146
|
+
error: { kind: 'unknown', userMessage: 'download timed out', detail: '', autoFixable: false },
|
|
147
|
+
});
|
|
148
|
+
}, timeout);
|
|
149
|
+
proc.on('exit', (code) => {
|
|
150
|
+
clearTimeout(timer);
|
|
151
|
+
if (code === 0) {
|
|
152
|
+
log(`OK ${Date.now() - started}ms ${cmd.slice(0, 200)}`);
|
|
153
|
+
resolve({ ok: true, stdout, stderr });
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
const combined = stderr || stdout;
|
|
157
|
+
const classified = classify(combined, code ?? 1);
|
|
158
|
+
log(`ERR ${Date.now() - started}ms kind=${classified.kind} code=${code} detail=${classified.detail}`);
|
|
159
|
+
resolve({ ok: false, stdout, stderr, error: classified });
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
proc.on('error', (err) => {
|
|
163
|
+
clearTimeout(timer);
|
|
164
|
+
resolve({
|
|
165
|
+
ok: false, stdout, stderr,
|
|
166
|
+
error: { kind: 'unknown', userMessage: err.message, detail: String(err), autoFixable: false },
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
export function updateCmd() {
|
|
172
|
+
const isTermux = !!process.env.TERMUX_VERSION || !!process.env.PREFIX?.includes('com.termux');
|
|
173
|
+
if (isTermux)
|
|
174
|
+
return 'pkg upgrade -y yt-dlp';
|
|
175
|
+
const p = os.platform();
|
|
176
|
+
if (p === 'linux')
|
|
177
|
+
return 'sudo apt install --only-upgrade -y yt-dlp || pip install --upgrade yt-dlp';
|
|
178
|
+
if (p === 'darwin')
|
|
179
|
+
return 'brew upgrade yt-dlp';
|
|
180
|
+
if (p === 'win32')
|
|
181
|
+
return 'winget upgrade yt-dlp.yt-dlp';
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
export async function updateYtdlp() {
|
|
185
|
+
const cmd = updateCmd();
|
|
186
|
+
if (!cmd)
|
|
187
|
+
return { ok: false, output: 'no update command for this platform' };
|
|
188
|
+
log(`UPDATE ${cmd}`);
|
|
189
|
+
try {
|
|
190
|
+
const { stdout, stderr } = await execAsync(cmd, {
|
|
191
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
192
|
+
timeout: 5 * 60 * 1000,
|
|
193
|
+
});
|
|
194
|
+
log(`UPDATE OK ${(stdout + stderr).slice(-300)}`);
|
|
195
|
+
return { ok: true, output: (stdout + stderr).slice(-1000) };
|
|
196
|
+
}
|
|
197
|
+
catch (err) {
|
|
198
|
+
const out = (err.stderr || '') + (err.stdout || '') + (err.message || '');
|
|
199
|
+
log(`UPDATE ERR ${out.slice(-300)}`);
|
|
200
|
+
return { ok: false, output: out.slice(-1000) };
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
export async function ytdlpVersion() {
|
|
204
|
+
try {
|
|
205
|
+
const { stdout } = await execAsync('yt-dlp --version');
|
|
206
|
+
return stdout.trim();
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
return 'not installed';
|
|
210
|
+
}
|
|
211
|
+
}
|