Resolve Node module lookup for puppeteer runner
This commit is contained in:
@@ -25,6 +25,54 @@ type logBuffer struct {
|
|||||||
prefix string
|
prefix string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// findNodeModuleBase attempts to locate a directory containing the required
|
||||||
|
// Puppeteer dependencies, starting from the current working directory and the
|
||||||
|
// executable's directory, walking up parent paths until a node_modules match is
|
||||||
|
// found. This allows the binary to resolve Node packages even when launched via
|
||||||
|
// a .desktop file or from another directory.
|
||||||
|
func findNodeModuleBase() (string, error) {
|
||||||
|
starts := []string{}
|
||||||
|
|
||||||
|
if wd, err := os.Getwd(); err == nil {
|
||||||
|
starts = append(starts, wd)
|
||||||
|
}
|
||||||
|
|
||||||
|
if exe, err := os.Executable(); err == nil {
|
||||||
|
exeDir := filepath.Dir(exe)
|
||||||
|
if exeDir != "" {
|
||||||
|
starts = append(starts, exeDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
seen := map[string]struct{}{}
|
||||||
|
for _, start := range starts {
|
||||||
|
dir := filepath.Clean(start)
|
||||||
|
for {
|
||||||
|
if _, ok := seen[dir]; ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
seen[dir] = struct{}{}
|
||||||
|
|
||||||
|
if dir == "" || dir == string(filepath.Separator) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
candidate := filepath.Join(dir, "node_modules", "puppeteer-extra", "package.json")
|
||||||
|
if _, err := os.Stat(candidate); err == nil {
|
||||||
|
return dir, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
parent := filepath.Dir(dir)
|
||||||
|
if parent == dir {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
dir = parent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", errors.New("puppeteer-extra not found; install dependencies with npm in the project directory")
|
||||||
|
}
|
||||||
|
|
||||||
func (l *logBuffer) Write(p []byte) (int, error) {
|
func (l *logBuffer) Write(p []byte) (int, error) {
|
||||||
if l.buf == nil {
|
if l.buf == nil {
|
||||||
l.buf = &bytes.Buffer{}
|
l.buf = &bytes.Buffer{}
|
||||||
@@ -64,23 +112,28 @@ func (l *logBuffer) WriteTo(w io.Writer) (int64, error) {
|
|||||||
return l.buf.WriteTo(w)
|
return l.buf.WriteTo(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ensurePuppeteerAvailable() error {
|
func ensurePuppeteerAvailable(baseDir string) error {
|
||||||
if _, err := exec.LookPath("node"); err != nil {
|
if _, err := exec.LookPath("node"); err != nil {
|
||||||
return fmt.Errorf("node executable not found: %w", err)
|
return fmt.Errorf("node executable not found: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify both puppeteer-extra and the stealth plugin are available from the
|
// Verify both puppeteer-extra and the stealth plugin are available from the
|
||||||
// project directory so the temporary runner can load them relative to cwd.
|
// discovered base directory so the temporary runner can load them reliably
|
||||||
|
// even when the binary is launched outside the repo (e.g., .desktop file).
|
||||||
requireScript := strings.Join([]string{
|
requireScript := strings.Join([]string{
|
||||||
"const { createRequire } = require('module');",
|
"const { createRequire } = require('module');",
|
||||||
"const req = createRequire(process.cwd() + '/');",
|
"const base = process.env.STREAMED_TUI_NODE_BASE || process.cwd();",
|
||||||
|
"const req = createRequire(base.endsWith('/') ? base : base + '/');",
|
||||||
"req.resolve('puppeteer-extra/package.json');",
|
"req.resolve('puppeteer-extra/package.json');",
|
||||||
"req.resolve('puppeteer-extra-plugin-stealth/package.json');",
|
"req.resolve('puppeteer-extra-plugin-stealth/package.json');",
|
||||||
}, "")
|
}, "")
|
||||||
|
|
||||||
check := exec.Command("node", "-e", requireScript)
|
check := exec.Command("node", "-e", requireScript)
|
||||||
|
check.Dir = baseDir
|
||||||
|
check.Env = append(os.Environ(), fmt.Sprintf("STREAMED_TUI_NODE_BASE=%s", baseDir))
|
||||||
|
|
||||||
if err := check.Run(); err != nil {
|
if err := check.Run(); err != nil {
|
||||||
return fmt.Errorf("puppeteer-extra or stealth plugin missing. Run `npm install puppeteer-extra puppeteer-extra-plugin-stealth puppeteer` in the project directory: %w", err)
|
return fmt.Errorf("puppeteer-extra or stealth plugin missing in %s. Run `npm install puppeteer-extra puppeteer-extra-plugin-stealth puppeteer` there: %w", baseDir, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -98,11 +151,16 @@ func extractM3U8Lite(embedURL string, log func(string)) (string, map[string]stri
|
|||||||
return "", nil, errors.New("empty embed URL")
|
return "", nil, errors.New("empty embed URL")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ensurePuppeteerAvailable(); err != nil {
|
baseDir, err := findNodeModuleBase()
|
||||||
|
if err != nil {
|
||||||
return "", nil, err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
runnerPath, err := writePuppeteerRunner()
|
if err := ensurePuppeteerAvailable(baseDir); err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
runnerPath, err := writePuppeteerRunner(baseDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
@@ -111,6 +169,8 @@ func extractM3U8Lite(embedURL string, log func(string)) (string, map[string]stri
|
|||||||
log(fmt.Sprintf("[puppeteer] launching chromium stealth runner for %s", embedURL))
|
log(fmt.Sprintf("[puppeteer] launching chromium stealth runner for %s", embedURL))
|
||||||
|
|
||||||
cmd := exec.Command("node", runnerPath, embedURL)
|
cmd := exec.Command("node", runnerPath, embedURL)
|
||||||
|
cmd.Dir = baseDir
|
||||||
|
cmd.Env = append(os.Environ(), fmt.Sprintf("STREAMED_TUI_NODE_BASE=%s", baseDir))
|
||||||
stdout := &logBuffer{buf: &bytes.Buffer{}, log: func(line string) { log(line) }, prefix: "[puppeteer stdout] "}
|
stdout := &logBuffer{buf: &bytes.Buffer{}, log: func(line string) { log(line) }, prefix: "[puppeteer stdout] "}
|
||||||
stderr := &logBuffer{buf: &bytes.Buffer{}, log: func(line string) { log(line) }, prefix: "[puppeteer stderr] "}
|
stderr := &logBuffer{buf: &bytes.Buffer{}, log: func(line string) { log(line) }, prefix: "[puppeteer stderr] "}
|
||||||
cmd.Stdout = stdout
|
cmd.Stdout = stdout
|
||||||
@@ -141,9 +201,10 @@ func extractM3U8Lite(embedURL string, log func(string)) (string, map[string]stri
|
|||||||
// writePuppeteerRunner materializes a temporary Node.js script that performs
|
// writePuppeteerRunner materializes a temporary Node.js script that performs
|
||||||
// the actual page load and .m3u8 discovery with puppeteer-extra stealth
|
// the actual page load and .m3u8 discovery with puppeteer-extra stealth
|
||||||
// protections.
|
// protections.
|
||||||
func writePuppeteerRunner() (string, error) {
|
func writePuppeteerRunner(baseDir string) (string, error) {
|
||||||
script := `const { createRequire } = require('module');
|
script := `const { createRequire } = require('module');
|
||||||
const requireFromCwd = createRequire(process.cwd() + '/');
|
const base = process.env.STREAMED_TUI_NODE_BASE || process.cwd();
|
||||||
|
const requireFromCwd = createRequire(base.endsWith('/') ? base : base + '/');
|
||||||
|
|
||||||
let puppeteer;
|
let puppeteer;
|
||||||
let StealthPlugin;
|
let StealthPlugin;
|
||||||
|
|||||||
Reference in New Issue
Block a user