feat(chats): parse LinkedIn MCP v4.22 inbox/conversation blobs. (#6)
get_inbox/get_conversation return a sections+references envelope, not a message list. Parser is covered by synthetic Alice/Bob fixtures; CI now runs the nested bin/chats tests. Session check no longer launches Chromium.
This commit is contained in:
@@ -6,33 +6,39 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
func checkLinkedInSession(userDataDir string) (bool, error) {
|
||||
cmd := exec.Command("uvx", "mcp-server-linkedin@latest",
|
||||
"--user-data-dir", userDataDir,
|
||||
"--no-auto-import",
|
||||
"--status",
|
||||
)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return true, fmt.Errorf("status check: %w\n%s", err, string(out))
|
||||
// Validate the source-session files without launching a browser. A full
|
||||
// `--status` run spawns Chromium and loads /feed/, doubling the automation
|
||||
// exposed to LinkedIn (429 rate limits) before the sync even starts.
|
||||
root := filepath.Dir(userDataDir)
|
||||
sessionFiles := []string{
|
||||
filepath.Join(root, "source-state.json"),
|
||||
filepath.Join(root, "cookies.json"),
|
||||
filepath.Join(userDataDir, "Default", "Cookies"),
|
||||
}
|
||||
return !strings.Contains(string(out), "✅"), nil
|
||||
for _, f := range sessionFiles {
|
||||
if _, err := os.Stat(f); err != nil {
|
||||
return true, fmt.Errorf("missing session file %s", f)
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func runSyncLinkedIn(args []string) int {
|
||||
fs := flag.NewFlagSet("chats sync linkedin", flag.ContinueOnError)
|
||||
limit := fs.Int("limit", 0, "max messages per conversation (0 = all)")
|
||||
refresh := fs.Bool("refresh", false, "refresh session from live webtop browser before sync")
|
||||
help := fs.Bool("help", false, "")
|
||||
fs.SetOutput(os.Stderr)
|
||||
if err := fs.Parse(args); err != nil {
|
||||
return 2
|
||||
}
|
||||
if *help {
|
||||
fmt.Fprintln(os.Stderr, "usage: chats sync linkedin [--limit N]")
|
||||
fmt.Fprintln(os.Stderr, "usage: chats sync linkedin [--limit N] [--refresh]")
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -42,15 +48,21 @@ func runSyncLinkedIn(args []string) int {
|
||||
userDataDir = home + "/.linkedin-mcp/profile"
|
||||
}
|
||||
|
||||
// Check session first
|
||||
if *refresh {
|
||||
if code := refreshLinkedInSession(userDataDir); code != 0 {
|
||||
return code
|
||||
}
|
||||
}
|
||||
|
||||
// Check session files first (no browser launch).
|
||||
loginNeeded, err := checkLinkedInSession(userDataDir)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "chats: linkedin status check: %v\n", err)
|
||||
}
|
||||
if loginNeeded {
|
||||
fmt.Fprintf(os.Stderr, "chats: LinkedIn session expired. Run:\n")
|
||||
fmt.Fprintf(os.Stderr, " uvx mcp-server-linkedin@latest --user-data-dir %s --login\n", userDataDir)
|
||||
fmt.Fprintf(os.Stderr, "Then retry 'chats sync linkedin'\n")
|
||||
fmt.Fprintf(os.Stderr, "chats: LinkedIn session missing. Run:\n")
|
||||
fmt.Fprintf(os.Stderr, " chats sync linkedin --refresh\n")
|
||||
fmt.Fprintf(os.Stderr, "or point LINKEDIN_USER_DATA_DIR at a valid session\n")
|
||||
return 1
|
||||
}
|
||||
|
||||
@@ -67,3 +79,27 @@ func runSyncLinkedIn(args []string) int {
|
||||
fmt.Printf("chats sync linkedin: completed in %s\n", time.Since(start).Round(time.Millisecond))
|
||||
return 0
|
||||
}
|
||||
|
||||
// refreshLinkedInSession re-syncs the LinkedIn source session from the live
|
||||
// webtop browser via the vendored refresh-linkedin-session helper.
|
||||
func refreshLinkedInSession(userDataDir string) int {
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "chats: resolve executable: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
helper := filepath.Join(filepath.Dir(exe), "refresh-linkedin-session")
|
||||
if _, err := os.Stat(helper); err != nil {
|
||||
// Fall back to the source tree helper next to this command file.
|
||||
helper = "bin/chats/refresh-linkedin-session"
|
||||
}
|
||||
root := filepath.Dir(userDataDir)
|
||||
cmd := exec.Command(helper, "--root", root)
|
||||
cmd.Stdout = os.Stderr
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "chats: linkedin session refresh: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user