@@ -98,31 +98,37 @@ def get_or_create_driver() -> Optional[webdriver.Chrome]:
98
98
# Add a page load timeout for safety
99
99
driver .set_page_load_timeout (60 )
100
100
101
- # Try to log in
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
117
- driver .quit ()
118
-
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
101
+ # Try to log in with retry loop
102
+ max_retries = 3
103
+ for attempt in range (max_retries ):
104
+ try :
105
+ if login_to_linkedin (driver ):
106
+ print ("Successfully logged in to LinkedIn" )
107
+ active_drivers [session_id ] = driver
108
+ return driver
109
+ except (
110
+ CaptchaRequiredError ,
111
+ InvalidCredentialsError ,
112
+ SecurityChallengeError ,
113
+ TwoFactorAuthError ,
114
+ RateLimitError ,
115
+ LoginTimeoutError ,
116
+ CredentialsNotFoundError ,
117
+ ) as e :
118
+ if config .chrome .non_interactive :
119
+ # In non-interactive mode, propagate the error
120
+ driver .quit ()
121
+ raise e
122
+ else :
123
+ # In interactive mode, handle the error and potentially retry
124
+ should_retry = handle_login_error (e )
125
+ if should_retry and attempt < max_retries - 1 :
126
+ print (f"🔄 Retry attempt { attempt + 2 } /{ max_retries } " )
127
+ continue
128
+ else :
129
+ # Clean up driver on final failure
130
+ driver .quit ()
131
+ return None
126
132
except Exception as e :
127
133
error_msg = f"🛑 Error creating web driver: { e } "
128
134
print (error_msg )
@@ -167,26 +173,74 @@ def login_to_linkedin(driver: webdriver.Chrome) -> bool:
167
173
168
174
from linkedin_scraper import actions # type: ignore
169
175
170
- # linkedin-scraper now handles all error detection and raises appropriate exceptions
171
- actions .login (
172
- driver ,
173
- credentials ["email" ],
174
- credentials ["password" ],
175
- interactive = not config .chrome .non_interactive ,
176
- )
176
+ # Use linkedin-scraper login but with simplified error handling
177
+ try :
178
+ actions .login (
179
+ driver ,
180
+ credentials ["email" ],
181
+ credentials ["password" ],
182
+ interactive = not config .chrome .non_interactive ,
183
+ )
184
+
185
+ print ("✅ Successfully logged in to LinkedIn" )
186
+ return True
177
187
178
- print ("✅ Successfully logged in to LinkedIn" )
179
- return True
188
+ except Exception :
189
+ # Check current page to determine the real issue
190
+ current_url = driver .current_url
180
191
192
+ if "checkpoint/challenge" in current_url :
193
+ # We're on a challenge page - this is the real issue, not credentials
194
+ if "security check" in driver .page_source .lower ():
195
+ raise SecurityChallengeError (
196
+ challenge_url = current_url ,
197
+ message = "LinkedIn requires a security challenge. Please complete it manually and restart the application." ,
198
+ )
199
+ else :
200
+ raise CaptchaRequiredError (
201
+ captcha_url = current_url ,
202
+ )
181
203
182
- def handle_login_error (error : Exception ) -> None :
183
- """Handle login errors in interactive mode."""
204
+ elif "feed" in current_url or "mynetwork" in current_url :
205
+ # Actually logged in successfully despite the exception
206
+ print ("✅ Successfully logged in to LinkedIn" )
207
+ return True
208
+
209
+ else :
210
+ # Check for actual credential issues
211
+ page_source = driver .page_source .lower ()
212
+ if any (
213
+ pattern in page_source
214
+ for pattern in ["wrong email" , "wrong password" , "incorrect" , "invalid" ]
215
+ ):
216
+ raise InvalidCredentialsError ("Invalid LinkedIn email or password." )
217
+ elif "too many" in page_source :
218
+ raise RateLimitError (
219
+ "Too many login attempts. Please wait and try again later."
220
+ )
221
+ else :
222
+ raise LoginTimeoutError (
223
+ "Login failed. Please check your credentials and network connection."
224
+ )
225
+
226
+
227
+ def handle_login_error (error : Exception ) -> bool :
228
+ """Handle login errors in interactive mode.
229
+
230
+ Returns:
231
+ bool: True if user wants to retry, False if they want to exit
232
+ """
184
233
config = get_config ()
185
234
186
- print (f"\n ❌ Login failed: { str (error )} " )
235
+ print (f"\n ❌ { str (error )} " )
236
+
237
+ if config .chrome .headless :
238
+ print (
239
+ "🔍 Try running with visible browser window: uv run main.py --no-headless"
240
+ )
187
241
242
+ # Only allow retry for credential errors
188
243
if isinstance (error , InvalidCredentialsError ):
189
- print ("⚠️ Please check your email and password." )
190
244
retry = inquirer .prompt (
191
245
[
192
246
inquirer .Confirm (
@@ -197,51 +251,12 @@ def handle_login_error(error: Exception) -> None:
197
251
]
198
252
)
199
253
if retry and retry .get ("retry" , False ):
200
- # Clear credentials from keyring
201
254
clear_credentials_from_keyring ()
202
255
print ("✅ Credentials cleared from keyring." )
203
- print ("💡 Please restart the application to try with new credentials." )
204
- print (" Example: uv run main.py --no-headless" )
205
-
206
- elif isinstance (error , CaptchaRequiredError ):
207
- print ("⚠️ LinkedIn requires captcha verification." )
208
- captcha_url = getattr (error , "captcha_url" , str (error ))
209
- print (f"🔗 Please complete the captcha at: { captcha_url } " )
210
- if config .chrome .headless :
211
- print (
212
- "🔍 Try running with visible browser window to complete captcha: "
213
- "uv run main.py --no-headless"
214
- )
256
+ print ("🔄 Retrying with new credentials..." )
257
+ return True
215
258
216
- elif isinstance (error , SecurityChallengeError ):
217
- print ("⚠️ LinkedIn requires a security challenge." )
218
- challenge_url = getattr (error , "challenge_url" , str (error ))
219
- print (f"🔗 Please complete the security challenge at: { challenge_url } " )
220
- if config .chrome .headless :
221
- print (
222
- "🔍 Try running with visible browser window to complete challenge: "
223
- "uv run main.py --no-headless"
224
- )
225
-
226
- elif isinstance (error , TwoFactorAuthError ):
227
- print ("⚠️ Two-factor authentication is required." )
228
- print (
229
- "📱 Please confirm the login in your LinkedIn mobile app or enter the 2FA code."
230
- )
231
- if config .chrome .headless :
232
- print (
233
- "🔍 Try running with visible browser window to complete 2FA: "
234
- "uv run main.py --no-headless"
235
- )
236
-
237
- elif isinstance (error , RateLimitError ):
238
- print ("⚠️ Too many login attempts. Please wait before trying again." )
239
-
240
- elif isinstance (error , LoginTimeoutError ):
241
- print ("⚠️ Login timed out. Please check your network connection." )
242
-
243
- else :
244
- print ("⚠️ An unexpected error occurred during login." )
259
+ return False
245
260
246
261
247
262
def initialize_driver () -> None :
@@ -277,11 +292,8 @@ def initialize_driver() -> None:
277
292
if driver :
278
293
print ("✅ Web driver initialized successfully" )
279
294
else :
280
- if config .chrome .non_interactive :
281
- raise DriverInitializationError (
282
- "Failed to initialize web driver in non-interactive mode"
283
- )
284
- print ("❌ Failed to initialize web driver." )
295
+ # Driver creation failed - always raise an error
296
+ raise DriverInitializationError ("Failed to initialize web driver" )
285
297
except (
286
298
CaptchaRequiredError ,
287
299
InvalidCredentialsError ,
@@ -291,11 +303,8 @@ def initialize_driver() -> None:
291
303
LoginTimeoutError ,
292
304
CredentialsNotFoundError ,
293
305
) as e :
294
- # In non-interactive mode, let the error propagate
295
- if config .chrome .non_interactive :
296
- raise e
297
- # In interactive mode, handle gracefully
298
- print (f"❌ Error: { str (e )} " )
306
+ # Always re-raise login-related errors so main.py can handle them
307
+ raise e
299
308
except WebDriverException as e :
300
309
if config .chrome .non_interactive :
301
310
raise DriverInitializationError (
0 commit comments