package signal import ( "bytes" "fmt" "io" "os" "os/exec" "path/filepath" "strings" "time" ) func (c *Client) LinkDevice(deviceName string) (string, error) { if c == nil { return "", fmt.Errorf("signal-cli not found in PATH") } if deviceName == "" { deviceName = DefaultDeviceName } if err := os.MkdirAll(c.ConfigPath, 0755); err != nil { return "", fmt.Errorf("failed to create config directory: %w", err) } dataDir := filepath.Join(c.ConfigPath, "data") if entries, err := os.ReadDir(c.ConfigPath); err == nil { for _, entry := range entries { if entry.IsDir() && strings.HasPrefix(entry.Name(), "+") { accountDir := filepath.Join(c.ConfigPath, entry.Name()) os.RemoveAll(accountDir) } } } os.RemoveAll(dataDir) cmd := exec.Command("signal-cli", "--config", c.ConfigPath, "link", "-n", deviceName) stdout, err := cmd.StdoutPipe() if err != nil { return "", fmt.Errorf("failed to create stdout pipe: %w", err) } stderr, err := cmd.StderrPipe() if err != nil { return "", fmt.Errorf("failed to create stderr pipe: %w", err) } if err := cmd.Start(); err != nil { return "", fmt.Errorf("failed to start link command: %w", err) } var output bytes.Buffer buf := make([]byte, 8192) done := make(chan string, 1) errChan := make(chan error, 1) go func() { for { n, err := stdout.Read(buf) if n > 0 { output.Write(buf[:n]) text := output.String() if idx := strings.Index(text, "sgnl://linkdevice"); idx != -1 { end := strings.IndexAny(text[idx:], " \n\r\t") var url string if end == -1 { url = text[idx:] } else { url = text[idx : idx+end] } done <- strings.TrimSpace(url) return } } if err != nil { if err != io.EOF { errChan <- err } return } } }() go func() { for { n, err := stderr.Read(buf) if n > 0 { output.Write(buf[:n]) text := output.String() if idx := strings.Index(text, "sgnl://linkdevice"); idx != -1 { end := strings.IndexAny(text[idx:], " \n\r\t") var url string if end == -1 { url = text[idx:] } else { url = text[idx : idx+end] } done <- strings.TrimSpace(url) return } } if err != nil { return } } }() select { case url := <-done: go func() { io.Copy(io.Discard, stdout) io.Copy(io.Discard, stderr) cmd.Wait() }() return url, nil case err := <-errChan: cmd.Process.Kill() return "", err case <-time.After(5 * time.Minute): cmd.Process.Kill() return "", fmt.Errorf("timeout waiting for QR code URL") } }