linkedin-automation-cli 1.0.0
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/.env.example +12 -0
- package/.github/workflows/ci.yml +66 -0
- package/.github/workflows/publish.yml +48 -0
- package/.husky/pre-commit +6 -0
- package/.prettierignore +4 -0
- package/.prettierrc +10 -0
- package/AGENTS.md +294 -0
- package/CHANGELOG.md +40 -0
- package/GIT_RELEASE.md +167 -0
- package/LICENSE +21 -0
- package/Makefile +30 -0
- package/NPM_PUBLISHING.md +230 -0
- package/PYEOF +0 -0
- package/README.md +295 -0
- package/TESTING-GUIDE.md +151 -0
- package/cmd/linkedin/main.go +9 -0
- package/dist/agent/action-executor.d.ts +81 -0
- package/dist/agent/action-executor.d.ts.map +1 -0
- package/dist/agent/action-executor.js +170 -0
- package/dist/agent/action-executor.js.map +1 -0
- package/dist/agent/action-executor.test.d.ts +2 -0
- package/dist/agent/action-executor.test.d.ts.map +1 -0
- package/dist/agent/action-executor.test.js +366 -0
- package/dist/agent/action-executor.test.js.map +1 -0
- package/dist/agent/claude-client.d.ts +74 -0
- package/dist/agent/claude-client.d.ts.map +1 -0
- package/dist/agent/claude-client.js +314 -0
- package/dist/agent/claude-client.js.map +1 -0
- package/dist/agent/claude-client.test.d.ts +2 -0
- package/dist/agent/claude-client.test.d.ts.map +1 -0
- package/dist/agent/claude-client.test.js +590 -0
- package/dist/agent/claude-client.test.js.map +1 -0
- package/dist/agent/dom-extractor.d.ts +50 -0
- package/dist/agent/dom-extractor.d.ts.map +1 -0
- package/dist/agent/dom-extractor.js +374 -0
- package/dist/agent/dom-extractor.js.map +1 -0
- package/dist/agent/dom-extractor.test.d.ts +7 -0
- package/dist/agent/dom-extractor.test.d.ts.map +1 -0
- package/dist/agent/dom-extractor.test.js +504 -0
- package/dist/agent/dom-extractor.test.js.map +1 -0
- package/dist/agent/extension-client.d.ts +75 -0
- package/dist/agent/extension-client.d.ts.map +1 -0
- package/dist/agent/extension-client.js +245 -0
- package/dist/agent/extension-client.js.map +1 -0
- package/dist/agent/index.d.ts +8 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent/index.js +16 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/agent/page-agent.d.ts +76 -0
- package/dist/agent/page-agent.d.ts.map +1 -0
- package/dist/agent/page-agent.js +236 -0
- package/dist/agent/page-agent.js.map +1 -0
- package/dist/agent/types.d.ts +236 -0
- package/dist/agent/types.d.ts.map +1 -0
- package/dist/agent/types.js +37 -0
- package/dist/agent/types.js.map +1 -0
- package/dist/cli/agent-commands.d.ts +3 -0
- package/dist/cli/agent-commands.d.ts.map +1 -0
- package/dist/cli/agent-commands.js +250 -0
- package/dist/cli/agent-commands.js.map +1 -0
- package/dist/cli/auth.d.ts +3 -0
- package/dist/cli/auth.d.ts.map +1 -0
- package/dist/cli/auth.js +288 -0
- package/dist/cli/auth.js.map +1 -0
- package/dist/cli/company.d.ts +3 -0
- package/dist/cli/company.d.ts.map +1 -0
- package/dist/cli/company.js +55 -0
- package/dist/cli/company.js.map +1 -0
- package/dist/cli/connection.d.ts +3 -0
- package/dist/cli/connection.d.ts.map +1 -0
- package/dist/cli/connection.js +79 -0
- package/dist/cli/connection.js.map +1 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +17 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/messages.d.ts +3 -0
- package/dist/cli/messages.d.ts.map +1 -0
- package/dist/cli/messages.js +268 -0
- package/dist/cli/messages.js.map +1 -0
- package/dist/cli/profile.d.ts +3 -0
- package/dist/cli/profile.d.ts.map +1 -0
- package/dist/cli/profile.js +81 -0
- package/dist/cli/profile.js.map +1 -0
- package/dist/cli/profile.test.d.ts +2 -0
- package/dist/cli/profile.test.d.ts.map +1 -0
- package/dist/cli/profile.test.js +15 -0
- package/dist/cli/profile.test.js.map +1 -0
- package/dist/cli/reply.d.ts +3 -0
- package/dist/cli/reply.d.ts.map +1 -0
- package/dist/cli/reply.js +129 -0
- package/dist/cli/reply.js.map +1 -0
- package/dist/core/audit.d.ts +17 -0
- package/dist/core/audit.d.ts.map +1 -0
- package/dist/core/audit.js +121 -0
- package/dist/core/audit.js.map +1 -0
- package/dist/core/audit.test.d.ts +2 -0
- package/dist/core/audit.test.d.ts.map +1 -0
- package/dist/core/audit.test.js +142 -0
- package/dist/core/audit.test.js.map +1 -0
- package/dist/core/browser-cookies.d.ts +19 -0
- package/dist/core/browser-cookies.d.ts.map +1 -0
- package/dist/core/browser-cookies.js +181 -0
- package/dist/core/browser-cookies.js.map +1 -0
- package/dist/core/browser.d.ts +50 -0
- package/dist/core/browser.d.ts.map +1 -0
- package/dist/core/browser.js +318 -0
- package/dist/core/browser.js.map +1 -0
- package/dist/core/config.d.ts +20 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +103 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/config.test.d.ts +2 -0
- package/dist/core/config.test.d.ts.map +1 -0
- package/dist/core/config.test.js +111 -0
- package/dist/core/config.test.js.map +1 -0
- package/dist/core/storage.d.ts +19 -0
- package/dist/core/storage.d.ts.map +1 -0
- package/dist/core/storage.js +124 -0
- package/dist/core/storage.js.map +1 -0
- package/dist/core/storage.test.d.ts +2 -0
- package/dist/core/storage.test.d.ts.map +1 -0
- package/dist/core/storage.test.js +142 -0
- package/dist/core/storage.test.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +63 -0
- package/dist/index.js.map +1 -0
- package/dist/linkedin/auth.d.ts +22 -0
- package/dist/linkedin/auth.d.ts.map +1 -0
- package/dist/linkedin/auth.js +167 -0
- package/dist/linkedin/auth.js.map +1 -0
- package/dist/linkedin/company-extractor.d.ts +36 -0
- package/dist/linkedin/company-extractor.d.ts.map +1 -0
- package/dist/linkedin/company-extractor.js +211 -0
- package/dist/linkedin/company-extractor.js.map +1 -0
- package/dist/linkedin/company-extractor.test.d.ts +2 -0
- package/dist/linkedin/company-extractor.test.d.ts.map +1 -0
- package/dist/linkedin/company-extractor.test.js +52 -0
- package/dist/linkedin/company-extractor.test.js.map +1 -0
- package/dist/linkedin/connector.d.ts +45 -0
- package/dist/linkedin/connector.d.ts.map +1 -0
- package/dist/linkedin/connector.js +245 -0
- package/dist/linkedin/connector.js.map +1 -0
- package/dist/linkedin/message-sender.d.ts +32 -0
- package/dist/linkedin/message-sender.d.ts.map +1 -0
- package/dist/linkedin/message-sender.js +112 -0
- package/dist/linkedin/message-sender.js.map +1 -0
- package/dist/linkedin/messages.d.ts +78 -0
- package/dist/linkedin/messages.d.ts.map +1 -0
- package/dist/linkedin/messages.js +745 -0
- package/dist/linkedin/messages.js.map +1 -0
- package/dist/linkedin/profile.d.ts +37 -0
- package/dist/linkedin/profile.d.ts.map +1 -0
- package/dist/linkedin/profile.js +268 -0
- package/dist/linkedin/profile.js.map +1 -0
- package/dist/linkedin/profile.test.d.ts +2 -0
- package/dist/linkedin/profile.test.d.ts.map +1 -0
- package/dist/linkedin/profile.test.js +68 -0
- package/dist/linkedin/profile.test.js.map +1 -0
- package/dist/linkedin/reply.d.ts +21 -0
- package/dist/linkedin/reply.d.ts.map +1 -0
- package/dist/linkedin/reply.js +76 -0
- package/dist/linkedin/reply.js.map +1 -0
- package/dist/linkedin/selector-engine.d.ts +69 -0
- package/dist/linkedin/selector-engine.d.ts.map +1 -0
- package/dist/linkedin/selector-engine.js +339 -0
- package/dist/linkedin/selector-engine.js.map +1 -0
- package/dist/linkedin/selector-engine.test.d.ts +2 -0
- package/dist/linkedin/selector-engine.test.d.ts.map +1 -0
- package/dist/linkedin/selector-engine.test.js +135 -0
- package/dist/linkedin/selector-engine.test.js.map +1 -0
- package/dist/linkedin/selectors.d.ts +65 -0
- package/dist/linkedin/selectors.d.ts.map +1 -0
- package/dist/linkedin/selectors.js +261 -0
- package/dist/linkedin/selectors.js.map +1 -0
- package/dist/templates/engine.d.ts +37 -0
- package/dist/templates/engine.d.ts.map +1 -0
- package/dist/templates/engine.js +215 -0
- package/dist/templates/engine.js.map +1 -0
- package/dist/templates/engine.test.d.ts +2 -0
- package/dist/templates/engine.test.d.ts.map +1 -0
- package/dist/templates/engine.test.js +212 -0
- package/dist/templates/engine.test.js.map +1 -0
- package/dist/templates/index.d.ts +2 -0
- package/dist/templates/index.d.ts.map +1 -0
- package/dist/templates/index.js +7 -0
- package/dist/templates/index.js.map +1 -0
- package/dist/types/index.d.ts +113 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/index.test.d.ts +2 -0
- package/dist/types/index.test.d.ts.map +1 -0
- package/dist/types/index.test.js +90 -0
- package/dist/types/index.test.js.map +1 -0
- package/dist/utils/paths.d.ts +8 -0
- package/dist/utils/paths.d.ts.map +1 -0
- package/dist/utils/paths.js +68 -0
- package/dist/utils/paths.js.map +1 -0
- package/dist/utils/rate-limiter.d.ts +22 -0
- package/dist/utils/rate-limiter.d.ts.map +1 -0
- package/dist/utils/rate-limiter.js +57 -0
- package/dist/utils/rate-limiter.js.map +1 -0
- package/dist/utils/retry.d.ts +18 -0
- package/dist/utils/retry.d.ts.map +1 -0
- package/dist/utils/retry.js +49 -0
- package/dist/utils/retry.js.map +1 -0
- package/docs/connection-command.md +52 -0
- package/docs/plans/2025-03-03-linkedin-cli-design.md +280 -0
- package/docs/plans/2025-03-03-linkedin-cli-implementation-plan.md +2087 -0
- package/docs/plans/2025-03-03-linkedin-cli-implementation.md +2420 -0
- package/docs/plans/2026-02-19-linkedin-connection-feature.md +596 -0
- package/docs/plans/2026-02-28-messages-send-feature.md +480 -0
- package/docs/plans/2026-02-28-messages-show-design.md +243 -0
- package/docs/plans/2026-03-03-linkedin-cli-oss-publishing-design.md +394 -0
- package/docs/plans/2026-03-03-linkedin-cli-oss-publishing-plan.md +1592 -0
- package/docs/superpowers/plans/2026-03-13-linkedin-automation-resilience-migration.md +425 -0
- package/docs/superpowers/plans/2026-03-13-playwright-fara-migration.md +1112 -0
- package/docs/superpowers/plans/2026-03-14-page-agent-plan.md +1598 -0
- package/docs/superpowers/plans/2026-03-15-company-profile-extraction.md +591 -0
- package/docs/superpowers/plans/2026-03-15-profile-extraction-plan.md +943 -0
- package/docs/superpowers/specs/2026-03-14-company-profile-extraction-design.md +371 -0
- package/docs/superpowers/specs/2026-03-14-page-agent-design.md +385 -0
- package/docs/superpowers/specs/2026-03-15-profile-extraction-design.md +409 -0
- package/eslint.config.mjs +58 -0
- package/go.mod +9 -0
- package/go.sum +10 -0
- package/import-cookies.js +376 -0
- package/internal/cmd/actions.go +123 -0
- package/internal/cmd/auth.go +108 -0
- package/internal/cmd/connect.go +42 -0
- package/internal/cmd/message.go +44 -0
- package/internal/cmd/people.go +454 -0
- package/internal/cmd/profiles.go +121 -0
- package/internal/cmd/root.go +89 -0
- package/internal/cmd/sequence.go +192 -0
- package/internal/config/config.go +187 -0
- package/internal/config/config_test.go +121 -0
- package/internal/config/profile.go +65 -0
- package/internal/linkedin/navigator.go +195 -0
- package/internal/linkedin/selectors.go +39 -0
- package/internal/linkedin/validator.go +69 -0
- package/internal/pinchtab/client.go +183 -0
- package/internal/pinchtab/client_test.go +67 -0
- package/internal/pinchtab/types.go +50 -0
- package/internal/ratelimit/limiter.go +115 -0
- package/internal/ratelimit/limits.go +32 -0
- package/package.json +67 -0
- package/release.sh +66 -0
- package/scripts/debug-linkedin.js +156 -0
- package/scripts/debug-login.js +193 -0
- package/scripts/extract-from-edge.js +96 -0
- package/scripts/import-cookies.js +101 -0
- package/scripts/poc-show-data.js +205 -0
- package/scripts/proof-of-access.js +87 -0
- package/scripts/prove-connection.js +110 -0
- package/scripts/show-linkedin-data.js +173 -0
- package/src/agent/action-executor.test.ts +464 -0
- package/src/agent/action-executor.ts +203 -0
- package/src/agent/claude-client.test.ts +707 -0
- package/src/agent/claude-client.ts +422 -0
- package/src/agent/dom-extractor.test.ts +574 -0
- package/src/agent/dom-extractor.ts +437 -0
- package/src/agent/extension-client.ts +306 -0
- package/src/agent/index.ts +28 -0
- package/src/agent/page-agent.ts +292 -0
- package/src/agent/types.ts +288 -0
- package/src/cli/agent-commands.ts +274 -0
- package/src/cli/auth.ts +343 -0
- package/src/cli/company.ts +66 -0
- package/src/cli/connection.ts +89 -0
- package/src/cli/index.ts +7 -0
- package/src/cli/messages.ts +338 -0
- package/src/cli/profile.test.ts +14 -0
- package/src/cli/profile.ts +95 -0
- package/src/cli/reply.ts +110 -0
- package/src/core/audit.test.ts +134 -0
- package/src/core/audit.ts +98 -0
- package/src/core/browser-cookies.ts +203 -0
- package/src/core/browser.ts +304 -0
- package/src/core/config.test.ts +90 -0
- package/src/core/config.ts +81 -0
- package/src/core/storage.test.ts +129 -0
- package/src/core/storage.ts +100 -0
- package/src/index.ts +70 -0
- package/src/linkedin/auth.ts +218 -0
- package/src/linkedin/company-extractor.test.ts +58 -0
- package/src/linkedin/company-extractor.ts +222 -0
- package/src/linkedin/connector.ts +336 -0
- package/src/linkedin/message-sender.ts +141 -0
- package/src/linkedin/messages.ts +894 -0
- package/src/linkedin/profile.test.ts +79 -0
- package/src/linkedin/profile.ts +314 -0
- package/src/linkedin/reply.ts +96 -0
- package/src/linkedin/selector-engine.test.ts +167 -0
- package/src/linkedin/selector-engine.ts +393 -0
- package/src/linkedin/selectors.ts +268 -0
- package/src/templates/defaults/followup.txt +14 -0
- package/src/templates/defaults/meeting.txt +16 -0
- package/src/templates/defaults/welcome.txt +14 -0
- package/src/templates/engine.test.ts +228 -0
- package/src/templates/engine.ts +208 -0
- package/src/templates/index.ts +1 -0
- package/src/types/index.test.ts +94 -0
- package/src/types/index.ts +143 -0
- package/src/types/sql.js.d.ts +23 -0
- package/src/utils/paths.ts +33 -0
- package/src/utils/rate-limiter.ts +75 -0
- package/src/utils/retry.ts +78 -0
- package/test-cli.sh +85 -0
- package/test-real-data.sh +97 -0
- package/tsconfig.json +23 -0
- package/vitest.config.ts +35 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
package ratelimit
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"math/rand"
|
|
6
|
+
"time"
|
|
7
|
+
|
|
8
|
+
"github.com/thaddeus-git/linkedin-cli/internal/config"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// Limiter handles rate limiting logic
|
|
12
|
+
type Limiter struct {
|
|
13
|
+
limits Limits
|
|
14
|
+
config *config.Manager
|
|
15
|
+
profile string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// NewLimiter creates a new rate limiter
|
|
19
|
+
func NewLimiter(profile string, limits Limits, cfg *config.Manager) *Limiter {
|
|
20
|
+
return &Limiter{
|
|
21
|
+
limits: limits,
|
|
22
|
+
config: cfg,
|
|
23
|
+
profile: profile,
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// CheckConnection checks if a connection request is allowed
|
|
28
|
+
func (l *Limiter) CheckConnection() error {
|
|
29
|
+
state, err := l.config.LoadRateLimits(l.profile)
|
|
30
|
+
if err != nil {
|
|
31
|
+
return fmt.Errorf("failed to load rate limits: %w", err)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if !state.CanConnect(l.limits.ConnectionsDaily, l.limits.ConnectionsWeekly) {
|
|
35
|
+
return fmt.Errorf(
|
|
36
|
+
"rate limit exceeded: %d/%d daily, %d/%d weekly connections",
|
|
37
|
+
state.Connections.Today,
|
|
38
|
+
l.limits.ConnectionsDaily,
|
|
39
|
+
state.Connections.ThisWeek,
|
|
40
|
+
l.limits.ConnectionsWeekly,
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return nil
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// CheckMessage checks if a message is allowed
|
|
48
|
+
func (l *Limiter) CheckMessage() error {
|
|
49
|
+
state, err := l.config.LoadRateLimits(l.profile)
|
|
50
|
+
if err != nil {
|
|
51
|
+
return fmt.Errorf("failed to load rate limits: %w", err)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if !state.CanMessage(l.limits.MessagesDaily) {
|
|
55
|
+
return fmt.Errorf(
|
|
56
|
+
"rate limit exceeded: %d/%d daily messages",
|
|
57
|
+
state.Messages.Today,
|
|
58
|
+
l.limits.MessagesDaily,
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return nil
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// RecordConnection records a connection request
|
|
66
|
+
func (l *Limiter) RecordConnection() error {
|
|
67
|
+
state, err := l.config.LoadRateLimits(l.profile)
|
|
68
|
+
if err != nil {
|
|
69
|
+
return err
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
state.RecordConnection()
|
|
73
|
+
return l.config.SaveRateLimits(l.profile, state)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// RecordMessage records a message
|
|
77
|
+
func (l *Limiter) RecordMessage() error {
|
|
78
|
+
state, err := l.config.LoadRateLimits(l.profile)
|
|
79
|
+
if err != nil {
|
|
80
|
+
return err
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
state.RecordMessage()
|
|
84
|
+
return l.config.SaveRateLimits(l.profile, state)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// GetDelay returns a random delay between min and max seconds
|
|
88
|
+
func (l *Limiter) GetDelay() time.Duration {
|
|
89
|
+
seconds := rand.Intn(l.limits.MaxDelaySeconds-l.limits.MinDelaySeconds+1) + l.limits.MinDelaySeconds
|
|
90
|
+
return time.Duration(seconds) * time.Second
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Sleep delays execution for a random duration
|
|
94
|
+
func (l *Limiter) Sleep() {
|
|
95
|
+
time.Sleep(l.GetDelay())
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Status returns current rate limit status
|
|
99
|
+
func (l *Limiter) Status() (map[string]interface{}, error) {
|
|
100
|
+
state, err := l.config.LoadRateLimits(l.profile)
|
|
101
|
+
if err != nil {
|
|
102
|
+
return nil, err
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return map[string]interface{}{
|
|
106
|
+
"connections_today": state.Connections.Today,
|
|
107
|
+
"connections_weekly": state.Connections.ThisWeek,
|
|
108
|
+
"connections_limit": l.limits.ConnectionsDaily,
|
|
109
|
+
"connections_weekly_limit": l.limits.ConnectionsWeekly,
|
|
110
|
+
"messages_today": state.Messages.Today,
|
|
111
|
+
"messages_limit": l.limits.MessagesDaily,
|
|
112
|
+
"last_connection": state.Connections.LastAction,
|
|
113
|
+
"last_message": state.Messages.LastAction,
|
|
114
|
+
}, nil
|
|
115
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
package ratelimit
|
|
2
|
+
|
|
3
|
+
// Limits defines rate limit thresholds
|
|
4
|
+
type Limits struct {
|
|
5
|
+
ConnectionsDaily int
|
|
6
|
+
ConnectionsWeekly int
|
|
7
|
+
MessagesDaily int
|
|
8
|
+
MinDelaySeconds int
|
|
9
|
+
MaxDelaySeconds int
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// DefaultLimits returns default LinkedIn-safe limits
|
|
13
|
+
func DefaultLimits() Limits {
|
|
14
|
+
return Limits{
|
|
15
|
+
ConnectionsDaily: 20,
|
|
16
|
+
ConnectionsWeekly: 100,
|
|
17
|
+
MessagesDaily: 50,
|
|
18
|
+
MinDelaySeconds: 3,
|
|
19
|
+
MaxDelaySeconds: 8,
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ConservativeLimits returns more conservative limits for new accounts
|
|
24
|
+
func ConservativeLimits() Limits {
|
|
25
|
+
return Limits{
|
|
26
|
+
ConnectionsDaily: 10,
|
|
27
|
+
ConnectionsWeekly: 50,
|
|
28
|
+
MessagesDaily: 25,
|
|
29
|
+
MinDelaySeconds: 5,
|
|
30
|
+
MaxDelaySeconds: 12,
|
|
31
|
+
}
|
|
32
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "linkedin-automation-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A CLI tool for LinkedIn automation using Playwright",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"linkedin-automation": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"start": "node dist/index.js",
|
|
12
|
+
"dev": "ts-node src/index.ts",
|
|
13
|
+
"watch": "nodemon --exec ts-node src/index.ts",
|
|
14
|
+
"clean": "rm -rf dist",
|
|
15
|
+
"typecheck": "tsc --noEmit",
|
|
16
|
+
"lint": "eslint src",
|
|
17
|
+
"lint:fix": "eslint src --fix",
|
|
18
|
+
"format": "prettier --write \"src/**/*.ts\"",
|
|
19
|
+
"format:check": "prettier --check \"src/**/*.ts\"",
|
|
20
|
+
"test": "vitest run",
|
|
21
|
+
"test:watch": "vitest",
|
|
22
|
+
"test:coverage": "vitest run --coverage",
|
|
23
|
+
"prepare": "husky"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"linkedin",
|
|
27
|
+
"automation",
|
|
28
|
+
"playwright",
|
|
29
|
+
"cli"
|
|
30
|
+
],
|
|
31
|
+
"author": "",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@types/ws": "^8.18.1",
|
|
35
|
+
"chalk": "^4.1.2",
|
|
36
|
+
"commander": "^11.1.0",
|
|
37
|
+
"dotenv": "^16.3.1",
|
|
38
|
+
"inquirer": "^9.2.12",
|
|
39
|
+
"ora": "^5.4.1",
|
|
40
|
+
"playwright": "^1.40.0",
|
|
41
|
+
"sql.js": "^1.14.0",
|
|
42
|
+
"ws": "^8.19.0",
|
|
43
|
+
"zod": "^3.22.4"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/inquirer": "^9.0.7",
|
|
47
|
+
"@types/node": "^20.10.0",
|
|
48
|
+
"@typescript-eslint/eslint-plugin": "^6.13.0",
|
|
49
|
+
"@typescript-eslint/parser": "^6.13.0",
|
|
50
|
+
"@vitest/coverage-v8": "^1.0.4",
|
|
51
|
+
"eslint": "^8.54.0",
|
|
52
|
+
"eslint-config-prettier": "^9.0.0",
|
|
53
|
+
"eslint-plugin-playwright": "^0.19.0",
|
|
54
|
+
"husky": "^9.0.6",
|
|
55
|
+
"nodemon": "^3.0.2",
|
|
56
|
+
"prettier": "^3.1.0",
|
|
57
|
+
"ts-node": "^10.9.1",
|
|
58
|
+
"typescript": "^5.3.2",
|
|
59
|
+
"vitest": "^1.0.4"
|
|
60
|
+
},
|
|
61
|
+
"engines": {
|
|
62
|
+
"node": ">=18.0.0"
|
|
63
|
+
},
|
|
64
|
+
"publishConfig": {
|
|
65
|
+
"access": "public"
|
|
66
|
+
}
|
|
67
|
+
}
|
package/release.sh
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
# LinkedIn CLI Release Script
|
|
4
|
+
# Usage: ./release.sh [patch|minor|major]
|
|
5
|
+
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
BUMP_TYPE="${1:-patch}"
|
|
9
|
+
WORKTREE_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
10
|
+
|
|
11
|
+
echo "đ LinkedIn CLI Release Script"
|
|
12
|
+
echo "=============================="
|
|
13
|
+
echo "Worktree: $WORKTREE_DIR"
|
|
14
|
+
echo "Version bump: $BUMP_TYPE"
|
|
15
|
+
echo ""
|
|
16
|
+
|
|
17
|
+
# Step 1: Run quality checks
|
|
18
|
+
echo "đ Running quality checks..."
|
|
19
|
+
npm run typecheck || { echo "â Type check failed"; exit 1; }
|
|
20
|
+
npm run lint || { echo "â Lint failed"; exit 1; }
|
|
21
|
+
npm run format || { echo "â Format failed"; exit 1; }
|
|
22
|
+
npm run test:coverage || { echo "â Tests failed"; exit 1; }
|
|
23
|
+
|
|
24
|
+
# Step 2: Build
|
|
25
|
+
echo "đ¨ Building project..."
|
|
26
|
+
npm run build || { echo "â Build failed"; exit 1; }
|
|
27
|
+
|
|
28
|
+
# Step 3: Commit changes
|
|
29
|
+
echo "đž Committing changes..."
|
|
30
|
+
git add -A
|
|
31
|
+
if git diff-index --quiet HEAD; then
|
|
32
|
+
echo "No changes to commit"
|
|
33
|
+
else
|
|
34
|
+
git commit -m "chore: release preparations (auto-generated)"
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
# Step 4: Merge to main
|
|
38
|
+
echo "đ Merging to main..."
|
|
39
|
+
git checkout main
|
|
40
|
+
git merge linkedin-automation-cli -m "Merge linkedin-automation-cli into main"
|
|
41
|
+
|
|
42
|
+
# Step 5: Push to GitHub
|
|
43
|
+
echo "âŹď¸ Pushing to GitHub..."
|
|
44
|
+
git push origin main
|
|
45
|
+
|
|
46
|
+
# Step 6: Create version tag
|
|
47
|
+
echo "đˇď¸ Creating version tag..."
|
|
48
|
+
npm version $BUMP_TYPE --no-git-tag-version
|
|
49
|
+
NEW_VERSION=$(node -p "require('./package.json').version")
|
|
50
|
+
git commit -am "chore: bump version to v$NEW_VERSION"
|
|
51
|
+
git tag -a "v$NEW_VERSION" -m "Release v$NEW_VERSION"
|
|
52
|
+
git push origin --tags
|
|
53
|
+
|
|
54
|
+
echo ""
|
|
55
|
+
echo "â
Release complete!"
|
|
56
|
+
echo "đŚ Version: v$NEW_VERSION"
|
|
57
|
+
echo "đ GitHub: https://github.com/thaddeus-git/linkedin-cli"
|
|
58
|
+
echo "đ Actions: https://github.com/thaddeus-git/linkedin-cli/actions"
|
|
59
|
+
echo ""
|
|
60
|
+
echo "GitHub Actions is now:"
|
|
61
|
+
echo " 1. Running CI checks"
|
|
62
|
+
echo " 2. Publishing to npm"
|
|
63
|
+
echo " 3. Creating GitHub Release"
|
|
64
|
+
echo ""
|
|
65
|
+
echo "Manual npm publish (if needed):"
|
|
66
|
+
echo " npm login && npm publish --access public"
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
const { chromium } = require('playwright');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
const CONFIG_DIR = path.join(require('os').homedir(), '.linkedin-cli');
|
|
6
|
+
const SESSIONS_DIR = path.join(CONFIG_DIR, 'sessions');
|
|
7
|
+
|
|
8
|
+
async function debugLinkedIn() {
|
|
9
|
+
console.log('đ LinkedIn Debug Tool');
|
|
10
|
+
console.log('======================\n');
|
|
11
|
+
|
|
12
|
+
// Load session
|
|
13
|
+
const sessionFile = path.join(SESSIONS_DIR, 'linkedin-session.json');
|
|
14
|
+
if (!fs.existsSync(sessionFile)) {
|
|
15
|
+
console.error('â No session found. Run auth login first.');
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const encrypted = JSON.parse(fs.readFileSync(sessionFile, 'utf8'));
|
|
20
|
+
|
|
21
|
+
// Simple decryption (matching the CLI's method)
|
|
22
|
+
const crypto = require('crypto');
|
|
23
|
+
const machineData = [
|
|
24
|
+
process.env.USER || process.env.USERNAME || 'unknown',
|
|
25
|
+
process.env.HOME || process.env.USERPROFILE || 'unknown',
|
|
26
|
+
process.platform,
|
|
27
|
+
].join('|');
|
|
28
|
+
|
|
29
|
+
const salt = Buffer.from(encrypted.salt, 'base64');
|
|
30
|
+
const key = crypto.pbkdf2Sync(machineData, salt, 100000, 32, 'sha256');
|
|
31
|
+
const iv = Buffer.from(encrypted.iv, 'base64');
|
|
32
|
+
const encryptedData = Buffer.from(encrypted.encrypted, 'base64');
|
|
33
|
+
const tag = Buffer.from(encrypted.tag, 'base64');
|
|
34
|
+
|
|
35
|
+
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
|
|
36
|
+
decipher.setAuthTag(tag);
|
|
37
|
+
const decrypted = Buffer.concat([decipher.update(encryptedData), decipher.final()]);
|
|
38
|
+
const sessionData = JSON.parse(decrypted.toString('utf8'));
|
|
39
|
+
|
|
40
|
+
console.log('â Session loaded');
|
|
41
|
+
console.log(` User ID: ${sessionData.cookies.find(c => c.name === 'li_at')?.value?.substring(0, 50)}...`);
|
|
42
|
+
|
|
43
|
+
// Launch browser
|
|
44
|
+
const browser = await chromium.launch({ headless: false });
|
|
45
|
+
const context = await browser.newContext({
|
|
46
|
+
userAgent: sessionData.userAgent,
|
|
47
|
+
viewport: { width: 1280, height: 800 }
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Set cookies
|
|
51
|
+
const cookies = sessionData.cookies.map(c => ({
|
|
52
|
+
name: c.name,
|
|
53
|
+
value: c.value,
|
|
54
|
+
domain: c.domain,
|
|
55
|
+
path: c.path || '/',
|
|
56
|
+
secure: c.secure,
|
|
57
|
+
httpOnly: false,
|
|
58
|
+
sameSite: 'Lax'
|
|
59
|
+
}));
|
|
60
|
+
|
|
61
|
+
await context.addCookies(cookies);
|
|
62
|
+
console.log('â Cookies set\n');
|
|
63
|
+
|
|
64
|
+
// Try to access feed
|
|
65
|
+
console.log('đ° Checking Feed...');
|
|
66
|
+
const feedPage = await context.newPage();
|
|
67
|
+
await feedPage.goto('https://www.linkedin.com/feed/', { waitUntil: 'domcontentloaded', timeout: 30000 });
|
|
68
|
+
await feedPage.waitForTimeout(3000);
|
|
69
|
+
|
|
70
|
+
const feedTitle = await feedPage.title();
|
|
71
|
+
console.log(` Page title: ${feedTitle}`);
|
|
72
|
+
|
|
73
|
+
// Take screenshot of feed
|
|
74
|
+
await feedPage.screenshot({ path: '/tmp/linkedin-feed.png', fullPage: false });
|
|
75
|
+
console.log(' đ¸ Screenshot saved: /tmp/linkedin-feed.png');
|
|
76
|
+
|
|
77
|
+
// Get feed posts
|
|
78
|
+
const posts = await feedPage.$$eval('.feed-shared-update-v2, .scaffold-finite-scroll__content > div', elements =>
|
|
79
|
+
elements.slice(0, 3).map(el => ({
|
|
80
|
+
text: el.innerText?.substring(0, 200),
|
|
81
|
+
author: el.querySelector('[class*="actor"], [class*="author"]')?.innerText
|
|
82
|
+
}))
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
if (posts.length > 0) {
|
|
86
|
+
console.log(` â Found ${posts.length} posts in feed`);
|
|
87
|
+
posts.forEach((post, i) => {
|
|
88
|
+
console.log(`\n Post ${i + 1}:`);
|
|
89
|
+
console.log(` Author: ${post.author || 'Unknown'}`);
|
|
90
|
+
console.log(` Preview: ${post.text?.substring(0, 100)}...`);
|
|
91
|
+
});
|
|
92
|
+
} else {
|
|
93
|
+
console.log(' â No posts found - checking page structure...');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Try to access messages
|
|
97
|
+
console.log('\nđŹ Checking Messages...');
|
|
98
|
+
const msgPage = await context.newPage();
|
|
99
|
+
await msgPage.goto('https://www.linkedin.com/messaging/', { waitUntil: 'domcontentloaded', timeout: 30000 });
|
|
100
|
+
await msgPage.waitForTimeout(3000);
|
|
101
|
+
|
|
102
|
+
const msgTitle = await msgPage.title();
|
|
103
|
+
console.log(` Page title: ${msgTitle}`);
|
|
104
|
+
|
|
105
|
+
// Take screenshot of messages
|
|
106
|
+
await msgPage.screenshot({ path: '/tmp/linkedin-messages.png', fullPage: false });
|
|
107
|
+
console.log(' đ¸ Screenshot saved: /tmp/linkedin-messages.png');
|
|
108
|
+
|
|
109
|
+
// Get conversation list
|
|
110
|
+
const conversations = await msgPage.$$eval('.msg-conversation-card, [data-testid="conversation-card"], div[class*="conversation"]', elements =>
|
|
111
|
+
elements.slice(0, 5).map(el => ({
|
|
112
|
+
name: el.querySelector('[class*="participant"], [class*="name"]')?.innerText,
|
|
113
|
+
preview: el.querySelector('[class*="preview"], [class*="message"]')?.innerText,
|
|
114
|
+
unread: el.querySelector('[class*="unread"]') !== null
|
|
115
|
+
}))
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
if (conversations.length > 0) {
|
|
119
|
+
console.log(` â Found ${conversations.length} conversations`);
|
|
120
|
+
conversations.forEach((conv, i) => {
|
|
121
|
+
console.log(`\n Conversation ${i + 1}:`);
|
|
122
|
+
console.log(` From: ${conv.name || 'Unknown'} ${conv.unread ? '(UNREAD)' : ''}`);
|
|
123
|
+
console.log(` Preview: ${conv.preview?.substring(0, 80) || 'No preview'}...`);
|
|
124
|
+
});
|
|
125
|
+
} else {
|
|
126
|
+
console.log(' â No conversations found');
|
|
127
|
+
|
|
128
|
+
// Debug: list all divs with class names containing 'conversation' or 'message'
|
|
129
|
+
const debugInfo = await msgPage.evaluate(() => {
|
|
130
|
+
const convElements = document.querySelectorAll('div[class*="conversation"], div[class*="message"], div[class*="msg-"]');
|
|
131
|
+
return Array.from(convElements).slice(0, 10).map(el => ({
|
|
132
|
+
className: el.className,
|
|
133
|
+
text: el.innerText?.substring(0, 50)
|
|
134
|
+
}));
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
if (debugInfo.length > 0) {
|
|
138
|
+
console.log(' đ Debug: Found elements with conversation/message classes:');
|
|
139
|
+
debugInfo.forEach((info, i) => {
|
|
140
|
+
console.log(` ${i + 1}. Class: ${info.className?.substring(0, 60)}...`);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
console.log('\nâ
Debug complete!');
|
|
146
|
+
console.log('\nScreenshots saved:');
|
|
147
|
+
console.log(' - /tmp/linkedin-feed.png');
|
|
148
|
+
console.log(' - /tmp/linkedin-messages.png');
|
|
149
|
+
|
|
150
|
+
await browser.close();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
debugLinkedIn().catch(error => {
|
|
154
|
+
console.error('â Error:', error.message);
|
|
155
|
+
process.exit(1);
|
|
156
|
+
});
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
const { chromium } = require('playwright');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const readline = require('readline');
|
|
5
|
+
|
|
6
|
+
const CONFIG_DIR = path.join(require('os').homedir(), '.linkedin-cli');
|
|
7
|
+
const SCREENSHOT_DIR = path.join(CONFIG_DIR, 'debug');
|
|
8
|
+
|
|
9
|
+
function askQuestion(query) {
|
|
10
|
+
const rl = readline.createInterface({
|
|
11
|
+
input: process.stdin,
|
|
12
|
+
output: process.stdout,
|
|
13
|
+
});
|
|
14
|
+
return new Promise(resolve => rl.question(query, ans => {
|
|
15
|
+
rl.close();
|
|
16
|
+
resolve(ans);
|
|
17
|
+
}));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
async function debugLogin() {
|
|
22
|
+
console.log('đ LinkedIn Login Debugger');
|
|
23
|
+
console.log('==========================\n');
|
|
24
|
+
|
|
25
|
+
if (!fs.existsSync(SCREENSHOT_DIR)) {
|
|
26
|
+
fs.mkdirSync(SCREENSHOT_DIR, { recursive: true });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Launch with maximum stealth
|
|
30
|
+
const browser = await chromium.launch({
|
|
31
|
+
headless: false,
|
|
32
|
+
executablePath: '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
|
|
33
|
+
args: [
|
|
34
|
+
'--disable-blink-features=AutomationControlled',
|
|
35
|
+
'--disable-web-security',
|
|
36
|
+
'--disable-features=IsolateOrigins,site-per-process',
|
|
37
|
+
]
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const context = await browser.newContext({
|
|
41
|
+
viewport: { width: 1920, height: 1080 },
|
|
42
|
+
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0',
|
|
43
|
+
locale: 'en-US',
|
|
44
|
+
timezoneId: 'America/New_York',
|
|
45
|
+
permissions: ['notifications'],
|
|
46
|
+
colorScheme: 'light',
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Inject stealth script
|
|
50
|
+
await context.addInitScript(() => {
|
|
51
|
+
Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
|
|
52
|
+
Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5] });
|
|
53
|
+
Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] });
|
|
54
|
+
window.chrome = { runtime: {} };
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const page = await context.newPage();
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
console.log('1ď¸âŁ Going to LinkedIn login page...');
|
|
61
|
+
await page.goto('https://www.linkedin.com/login', {
|
|
62
|
+
waitUntil: 'networkidle',
|
|
63
|
+
timeout: 60000
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
await page.waitForTimeout(3000);
|
|
67
|
+
await page.screenshot({ path: path.join(SCREENSHOT_DIR, '01-login-page.png') });
|
|
68
|
+
console.log(' đ¸ Screenshot saved: 01-login-page.png');
|
|
69
|
+
|
|
70
|
+
console.log('\n2ď¸âŁ Filling email...');
|
|
71
|
+
await page.fill('input#username', '628552@qq.com', { delay: 100 });
|
|
72
|
+
await page.waitForTimeout(1000);
|
|
73
|
+
|
|
74
|
+
console.log('3ď¸âŁ Prompting for password...');
|
|
75
|
+
const password = await askQuestion('Enter your LinkedIn password: ');
|
|
76
|
+
console.log(' (Password received, filling form...)');
|
|
77
|
+
await page.fill('input#password', password, { delay: 100 });
|
|
78
|
+
await page.fill('input#password', process.env.LINKEDIN_PASSWORD || '', { delay: 100 });
|
|
79
|
+
await page.waitForTimeout(1000);
|
|
80
|
+
|
|
81
|
+
console.log('4ď¸âŁ Clicking submit...');
|
|
82
|
+
await page.click('button[type="submit"]');
|
|
83
|
+
|
|
84
|
+
console.log('5ď¸âŁ Waiting for navigation...');
|
|
85
|
+
await page.waitForTimeout(5000);
|
|
86
|
+
|
|
87
|
+
await page.screenshot({ path: path.join(SCREENSHOT_DIR, '02-after-submit.png') });
|
|
88
|
+
console.log(' đ¸ Screenshot saved: 02-after-submit.png');
|
|
89
|
+
|
|
90
|
+
// Check current URL
|
|
91
|
+
const currentUrl = page.url();
|
|
92
|
+
console.log(`\nđ Current URL: ${currentUrl}`);
|
|
93
|
+
|
|
94
|
+
// Check if we're on 2FA page
|
|
95
|
+
const has2FA = await page.$('input#input__phone_verification_pin') !== null;
|
|
96
|
+
const hasCaptcha = await page.$('[data-testid="captcha-internal"]') !== null ||
|
|
97
|
+
await page.$('.captcha') !== null ||
|
|
98
|
+
await page.$('text=Security verification') !== null;
|
|
99
|
+
|
|
100
|
+
if (hasCaptcha) {
|
|
101
|
+
console.log('\nđ¨ CAPTCHA DETECTED!');
|
|
102
|
+
console.log(' LinkedIn is showing a security challenge.');
|
|
103
|
+
console.log(' You need to manually solve it in the browser window.');
|
|
104
|
+
await page.screenshot({ path: path.join(SCREENSHOT_DIR, '03-captcha.png') });
|
|
105
|
+
|
|
106
|
+
console.log('\nâł Waiting 60 seconds for you to solve CAPTCHA...');
|
|
107
|
+
await page.waitForTimeout(60000);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (has2FA) {
|
|
111
|
+
console.log('\nđ 2FA Prompt Detected');
|
|
112
|
+
console.log(' Please enter the 6-digit code from Microsoft Authenticator');
|
|
113
|
+
|
|
114
|
+
// Wait for manual 2FA entry
|
|
115
|
+
await page.waitForTimeout(15000);
|
|
116
|
+
|
|
117
|
+
await page.screenshot({ path: path.join(SCREENSHOT_DIR, '03-2fa.png') });
|
|
118
|
+
console.log(' đ¸ Screenshot saved: 03-2fa.png');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Check if we're logged in
|
|
122
|
+
const isLoggedIn = await page.$('header.global-nav') !== null ||
|
|
123
|
+
await page.$('[data-testid="global-nav"]') !== null;
|
|
124
|
+
|
|
125
|
+
if (isLoggedIn) {
|
|
126
|
+
console.log('\nâ
SUCCESSFULLY LOGGED IN!');
|
|
127
|
+
|
|
128
|
+
// Save session
|
|
129
|
+
const cookies = await context.cookies();
|
|
130
|
+
const sessionData = {
|
|
131
|
+
cookies,
|
|
132
|
+
timestamp: Date.now(),
|
|
133
|
+
userAgent: await page.evaluate(() => navigator.userAgent)
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// Encrypt and save (simplified)
|
|
137
|
+
const sessionFile = path.join(CONFIG_DIR, 'sessions', 'linkedin-session.json');
|
|
138
|
+
fs.mkdirSync(path.dirname(sessionFile), { recursive: true });
|
|
139
|
+
|
|
140
|
+
// Use same encryption as CLI
|
|
141
|
+
const crypto = require('crypto');
|
|
142
|
+
const machineData = [process.env.USER, process.env.HOME, process.platform].join('|');
|
|
143
|
+
const salt = crypto.randomBytes(32);
|
|
144
|
+
const key = crypto.pbkdf2Sync(machineData, salt, 100000, 32, 'sha256');
|
|
145
|
+
const iv = crypto.randomBytes(16);
|
|
146
|
+
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
|
|
147
|
+
const encrypted = Buffer.concat([cipher.update(JSON.stringify(sessionData)), cipher.final()]);
|
|
148
|
+
const tag = cipher.getAuthTag();
|
|
149
|
+
|
|
150
|
+
fs.writeFileSync(sessionFile, JSON.stringify({
|
|
151
|
+
encrypted: encrypted.toString('base64'),
|
|
152
|
+
iv: iv.toString('base64'),
|
|
153
|
+
salt: salt.toString('base64'),
|
|
154
|
+
tag: tag.toString('base64')
|
|
155
|
+
}, null, 2));
|
|
156
|
+
|
|
157
|
+
console.log(` đž Session saved to: ${sessionFile}`);
|
|
158
|
+
|
|
159
|
+
// Test feed access
|
|
160
|
+
console.log('\n6ď¸âŁ Testing feed access...');
|
|
161
|
+
await page.goto('https://www.linkedin.com/feed/', { waitUntil: 'networkidle' });
|
|
162
|
+
await page.waitForTimeout(3000);
|
|
163
|
+
await page.screenshot({ path: path.join(SCREENSHOT_DIR, '04-feed.png') });
|
|
164
|
+
console.log(' đ¸ Screenshot saved: 04-feed.png');
|
|
165
|
+
console.log(` đ Page title: ${await page.title()}`);
|
|
166
|
+
|
|
167
|
+
} else {
|
|
168
|
+
console.log('\nâ Login verification failed');
|
|
169
|
+
await page.screenshot({ path: path.join(SCREENSHOT_DIR, '03-failed.png') });
|
|
170
|
+
console.log(' đ¸ Screenshot saved: 03-failed.png');
|
|
171
|
+
|
|
172
|
+
// Get page content for debugging
|
|
173
|
+
const content = await page.content();
|
|
174
|
+
fs.writeFileSync(path.join(SCREENSHOT_DIR, 'page-content.html'), content);
|
|
175
|
+
console.log(' đ Page HTML saved: page-content.html');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
console.log(`\nđ All debug files saved to: ${SCREENSHOT_DIR}`);
|
|
179
|
+
console.log('\nBrowser will stay open for 30 seconds...');
|
|
180
|
+
await page.waitForTimeout(30000);
|
|
181
|
+
|
|
182
|
+
} catch (error) {
|
|
183
|
+
console.error('â Error:', error.message);
|
|
184
|
+
await page.screenshot({ path: path.join(SCREENSHOT_DIR, 'error.png') });
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
await browser.close();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
console.log('This script will help debug the LinkedIn login process.');
|
|
191
|
+
console.log('It will open a visible browser and take screenshots at each step.\n');
|
|
192
|
+
|
|
193
|
+
debugLogin().catch(console.error);
|