18
18
from linkedin_mcp_server .config import get_config
19
19
from linkedin_mcp_server .config .providers import clear_credentials_from_keyring
20
20
from linkedin_mcp_server .config .secrets import get_credentials
21
+ from linkedin_scraper .exceptions import (
22
+ CaptchaRequiredError ,
23
+ InvalidCredentialsError ,
24
+ LoginTimeoutError ,
25
+ RateLimitError ,
26
+ SecurityChallengeError ,
27
+ TwoFactorAuthError ,
28
+ )
29
+ from linkedin_mcp_server .exceptions import (
30
+ CredentialsNotFoundError ,
31
+ DriverInitializationError ,
32
+ )
21
33
22
34
# Global driver storage to reuse sessions
23
35
active_drivers : Dict [str , webdriver .Chrome ] = {}
@@ -87,24 +99,41 @@ def get_or_create_driver() -> Optional[webdriver.Chrome]:
87
99
driver .set_page_load_timeout (60 )
88
100
89
101
# Try to log in
90
- if login_to_linkedin (driver ):
91
- print ("Successfully logged in to LinkedIn" )
92
- elif config .chrome .non_interactive :
93
- # In non-interactive mode, if login fails, return None
102
+ try :
103
+ if login_to_linkedin (driver ):
104
+ print ("Successfully logged in to LinkedIn" )
105
+ active_drivers [session_id ] = driver
106
+ return driver
107
+ except (
108
+ CaptchaRequiredError ,
109
+ InvalidCredentialsError ,
110
+ SecurityChallengeError ,
111
+ TwoFactorAuthError ,
112
+ RateLimitError ,
113
+ LoginTimeoutError ,
114
+ CredentialsNotFoundError ,
115
+ ) as e :
116
+ # Clean up driver on login failure
94
117
driver .quit ()
95
- return None
96
118
97
- active_drivers [session_id ] = driver
98
- return driver
119
+ if config .chrome .non_interactive :
120
+ # In non-interactive mode, propagate the error
121
+ raise e
122
+ else :
123
+ # In interactive mode, handle the error
124
+ handle_login_error (e )
125
+ return None
99
126
except Exception as e :
100
127
error_msg = f"🛑 Error creating web driver: { e } "
101
128
print (error_msg )
102
129
103
130
if config .chrome .non_interactive :
104
- print ("🛑 Failed to initialize driver in non-interactive mode" )
105
- return None
131
+ raise DriverInitializationError (error_msg )
132
+ else :
133
+ raise WebDriverException (error_msg )
134
+
106
135
107
- raise WebDriverException ( error_msg )
136
+ # Remove this function - linkedin-scraper now handles all error detection
108
137
109
138
110
139
def login_to_linkedin (driver : webdriver .Chrome ) -> bool :
@@ -116,59 +145,105 @@ def login_to_linkedin(driver: webdriver.Chrome) -> bool:
116
145
117
146
Returns:
118
147
bool: True if login was successful, False otherwise
148
+
149
+ Raises:
150
+ Various login-related errors from linkedin-scraper
119
151
"""
120
152
config = get_config ()
121
153
122
154
# Get LinkedIn credentials from config
123
- credentials = get_credentials ()
155
+ try :
156
+ credentials = get_credentials ()
157
+ except CredentialsNotFoundError as e :
158
+ if config .chrome .non_interactive :
159
+ raise e
160
+ # Only prompt if not in non-interactive mode
161
+ from linkedin_mcp_server .config .secrets import prompt_for_credentials
162
+
163
+ credentials = prompt_for_credentials ()
124
164
125
165
if not credentials :
126
- print ("❌ No credentials available" )
127
- return False
166
+ raise CredentialsNotFoundError ("No credentials available" )
128
167
129
- try :
130
- # Login to LinkedIn
131
- print ("🔑 Logging in to LinkedIn..." )
168
+ # Login to LinkedIn using enhanced linkedin-scraper
169
+ print ("🔑 Logging in to LinkedIn..." )
132
170
133
- from linkedin_scraper import actions # type: ignore
171
+ from linkedin_scraper import actions # type: ignore
134
172
135
- actions .login (driver , credentials ["email" ], credentials ["password" ])
173
+ # linkedin-scraper now handles all error detection and raises appropriate exceptions
174
+ actions .login (
175
+ driver ,
176
+ credentials ["email" ],
177
+ credentials ["password" ],
178
+ interactive = not config .chrome .non_interactive ,
179
+ )
136
180
137
- print ("✅ Successfully logged in to LinkedIn" )
138
- return True
139
- except Exception as e :
140
- error_msg = f"Failed to login: { str (e )} "
141
- print (f"❌ { error_msg } " )
181
+ print ("✅ Successfully logged in to LinkedIn" )
182
+ return True
142
183
143
- if not config .chrome .non_interactive :
184
+
185
+ def handle_login_error (error : Exception ) -> None :
186
+ """Handle login errors in interactive mode."""
187
+ config = get_config ()
188
+
189
+ print (f"\n ❌ Login failed: { str (error )} " )
190
+
191
+ if isinstance (error , InvalidCredentialsError ):
192
+ print ("⚠️ Please check your email and password." )
193
+ retry = inquirer .prompt (
194
+ [
195
+ inquirer .Confirm (
196
+ "retry" ,
197
+ message = "Would you like to try with different credentials?" ,
198
+ default = True ,
199
+ ),
200
+ ]
201
+ )
202
+ if retry and retry .get ("retry" , False ):
203
+ # Clear credentials from keyring and try again
204
+ clear_credentials_from_keyring ()
205
+ # Try again
206
+ initialize_driver ()
207
+
208
+ elif isinstance (error , CaptchaRequiredError ):
209
+ print ("⚠️ LinkedIn requires captcha verification." )
210
+ captcha_url = getattr (error , "captcha_url" , str (error ))
211
+ print (f"🔗 Please complete the captcha at: { captcha_url } " )
212
+ if config .chrome .headless :
144
213
print (
145
- "⚠️ You might need to confirm the login in your LinkedIn mobile app. "
146
- "Please try again and confirm the login. "
214
+ "🔍 Try running with visible browser window to complete captcha: "
215
+ "uv run main.py --no-headless "
147
216
)
148
217
149
- if config .chrome .headless :
150
- print (
151
- "🔍 Try running with visible browser window to see what's happening: "
152
- "uv run main.py --no-headless"
153
- )
218
+ elif isinstance (error , SecurityChallengeError ):
219
+ print ("⚠️ LinkedIn requires a security challenge." )
220
+ challenge_url = getattr (error , "challenge_url" , str (error ))
221
+ print (f"🔗 Please complete the security challenge at: { challenge_url } " )
222
+ if config .chrome .headless :
223
+ print (
224
+ "🔍 Try running with visible browser window to complete challenge: "
225
+ "uv run main.py --no-headless"
226
+ )
154
227
155
- retry = inquirer .prompt (
156
- [
157
- inquirer .Confirm (
158
- "retry" ,
159
- message = "Would you like to try with different credentials?" ,
160
- default = True ,
161
- ),
162
- ]
228
+ elif isinstance (error , TwoFactorAuthError ):
229
+ print ("⚠️ Two-factor authentication is required." )
230
+ print (
231
+ "📱 Please confirm the login in your LinkedIn mobile app or enter the 2FA code."
232
+ )
233
+ if config .chrome .headless :
234
+ print (
235
+ "🔍 Try running with visible browser window to complete 2FA: "
236
+ "uv run main.py --no-headless"
163
237
)
164
238
165
- if retry and retry .get ("retry" , False ):
166
- # Clear credentials from keyring and try again
167
- clear_credentials_from_keyring ()
168
- # Try again with new credentials
169
- return login_to_linkedin (driver )
239
+ elif isinstance (error , RateLimitError ):
240
+ print ("⚠️ Too many login attempts. Please wait before trying again." )
170
241
171
- return False
242
+ elif isinstance (error , LoginTimeoutError ):
243
+ print ("⚠️ Login timed out. Please check your network connection." )
244
+
245
+ else :
246
+ print ("⚠️ An unexpected error occurred during login." )
172
247
173
248
174
249
def initialize_driver () -> None :
@@ -204,8 +279,30 @@ def initialize_driver() -> None:
204
279
if driver :
205
280
print ("✅ Web driver initialized successfully" )
206
281
else :
282
+ if config .chrome .non_interactive :
283
+ raise DriverInitializationError (
284
+ "Failed to initialize web driver in non-interactive mode"
285
+ )
207
286
print ("❌ Failed to initialize web driver." )
287
+ except (
288
+ CaptchaRequiredError ,
289
+ InvalidCredentialsError ,
290
+ SecurityChallengeError ,
291
+ TwoFactorAuthError ,
292
+ RateLimitError ,
293
+ LoginTimeoutError ,
294
+ CredentialsNotFoundError ,
295
+ ) as e :
296
+ # In non-interactive mode, let the error propagate
297
+ if config .chrome .non_interactive :
298
+ raise e
299
+ # In interactive mode, handle gracefully
300
+ print (f"❌ Error: { str (e )} " )
208
301
except WebDriverException as e :
302
+ if config .chrome .non_interactive :
303
+ raise DriverInitializationError (
304
+ f"Failed to initialize web driver: { str (e )} "
305
+ )
209
306
print (f"❌ Failed to initialize web driver: { str (e )} " )
210
307
handle_driver_error ()
211
308
@@ -216,6 +313,11 @@ def handle_driver_error() -> None:
216
313
"""
217
314
config = get_config ()
218
315
316
+ # Skip interactive handling in non-interactive mode
317
+ if config .chrome .non_interactive :
318
+ print ("❌ ChromeDriver is required for this application to work properly." )
319
+ sys .exit (1 )
320
+
219
321
questions = [
220
322
inquirer .List (
221
323
"chromedriver_action" ,
0 commit comments