├── config.json ├── mail_connection.py └── walmart_session.py /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "walmart_login_information": { 3 | "email": "walmart login email" 4 | }, 5 | 6 | "mail_login_information":{ 7 | "server": "server address here", 8 | "port": "server port here - as integer", 9 | "username": "mail username", 10 | "password": "mail password" 11 | } 12 | } -------------------------------------------------------------------------------- /mail_connection.py: -------------------------------------------------------------------------------- 1 | from email.header import decode_header 2 | from email import message_from_bytes 3 | from imaplib import IMAP4_SSL 4 | from time import sleep 5 | from re import search 6 | 7 | import user_interface 8 | 9 | class MailConnection: 10 | def __init__(self, server: str, port: int, username: str, password: str) -> None: 11 | self.emails: list = [] 12 | self.otp: str = "" 13 | 14 | self.mail_server = IMAP4_SSL(server, port) 15 | self.connect_to_server(username, password) 16 | 17 | def decode_mime(self, string: str) -> str | None: 18 | if string is None: 19 | return None 20 | 21 | decoded_fragments: list = decode_header(string) 22 | fragments: list = [] 23 | 24 | for fragment, encoding in decoded_fragments: 25 | if isinstance(fragment, bytes): 26 | if encoding: 27 | fragments.append(fragment.decode(encoding)) 28 | 29 | else: 30 | fragments.append(fragment.decode("utf-8", errors="ignore")) 31 | 32 | else: 33 | fragments.append(fragment) 34 | 35 | return "".join(fragments) 36 | 37 | def connect_to_server(self, username: str, password: str) -> bool: 38 | try: 39 | self.mail_server.login(username, password) 40 | return True 41 | 42 | except: 43 | pass 44 | 45 | return False 46 | 47 | def move_to_trash(self, email_id: str, code: str) -> bool: 48 | try: 49 | self.mail_server.select("INBOX") 50 | 51 | result = self.mail_server.copy(str(email_id), "Trash") 52 | 53 | if result[0] == "OK": 54 | self.mail_server.store(str(email_id), "+FLAGS", "\\Deleted") 55 | self.mail_server.expunge() 56 | return True 57 | 58 | except: 59 | pass 60 | 61 | return False 62 | 63 | def fetch_otp(self, max_attempts: int = 7) -> str | None: 64 | for attempt in range(max_attempts): 65 | try: 66 | self.mail_server.select("INBOX") 67 | status, messages = self.mail_server.search(None, "UNDELETED") 68 | email_ids = messages[0].split() 69 | email_ids = email_ids[-5:] 70 | 71 | self.emails = [] 72 | 73 | for email_id in reversed(email_ids): 74 | status, data = self.mail_server.fetch(email_id, "(BODY[HEADER.FIELDS (SUBJECT FROM)])") 75 | 76 | if status == "OK": 77 | msg = message_from_bytes(data[0][1]) 78 | 79 | subject_data = msg.get("Subject", "") 80 | subject = subject_data.strip() 81 | subject = self.decode_mime(subject) 82 | 83 | from_data = msg.get("From", "") 84 | from_email = self.decode_mime(from_data) 85 | 86 | email_match = search(r'<([^>]+)>|([^\s<>]+@[^\s<>]+)', from_email) 87 | sender_email = email_match.group(1) or email_match.group(2) if email_match else from_email 88 | 89 | email_info = { 90 | "id": email_id.decode(), 91 | "subject": subject, 92 | "sender_email": sender_email.strip() 93 | } 94 | self.emails.append(email_info) 95 | 96 | if self.emails: 97 | for email_info in self.emails: 98 | if email_info["sender_email"] == "help@walmart.com": 99 | code_match = search(r'\b(\d{6})\b', email_info["subject"]) 100 | 101 | if code_match: 102 | code = code_match.group(1) 103 | email_id = email_info["id"] 104 | self.move_to_trash(email_id, code) 105 | return code.strip() 106 | 107 | if attempt < max_attempts - 1: 108 | sleep(5) 109 | continue 110 | 111 | return None 112 | 113 | except: 114 | if attempt < max_attempts - 1: 115 | sleep(5) 116 | continue 117 | 118 | return None 119 | -------------------------------------------------------------------------------- /walmart_session.py: -------------------------------------------------------------------------------- 1 | from string import ascii_lowercase, ascii_letters, digits 2 | from secrets import token_hex, token_bytes 3 | from requests import Session, Response 4 | from base64 import urlsafe_b64encode 5 | from json import load, loads, dumps 6 | from urllib.parse import quote 7 | from hashlib import sha256 8 | from random import choice 9 | from re import search 10 | 11 | import mail_connection 12 | 13 | class WalmartSession: 14 | def __init__(self) -> None: 15 | with open("config.json") as cfg: 16 | config: dict = load(cfg) 17 | 18 | self.email: str = config["walmart_login_information"]["email"] 19 | self.server: str = config["mail_login_information"]["server"] 20 | self.port: int = config["mail_login_information"]["port"] 21 | self.username: str = config["mail_login_information"]["username"] 22 | self.password: str = config["mail_login_information"]["password"] 23 | self.code_challenge = None 24 | self.code_verifier = None 25 | 26 | self.session: Session = Session() 27 | self.session.headers.update({ 28 | "Authority": "www.walmart.com", 29 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8", 30 | "Content-Type": "application/json", 31 | "Accept-Encoding": "gzip, deflate, br, zstd", 32 | "Accept-Language": "en-US,en;q=0.7", 33 | "Cache-Control": "max-age=0", 34 | "Priority": "u=0 i", 35 | "Referer": "https://www.walmart.com/", 36 | "Sec-Ch-Ua": '"Chromium";v="142", "Brave";v="142", "Not_A Brand";v="99"', 37 | "Sec-Ch-Ua-Mobile": "?0", 38 | "Sec-Ch-Ua-Platform": '"Windows"', 39 | "Sec-Fetch-Dest": "document", 40 | "Sec-Fetch-Mode": "navigate", 41 | "Sec-Fetch-Site": "same-origin", 42 | "Sec-Fetch-User": "?1", 43 | "Sec-Gpc": "1", 44 | "Upgrade-Insecure-Requests": "1", 45 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36" 46 | }) 47 | 48 | self.correlation_id: str = self.generate_correlation_id() 49 | self.device_profile: str = self.generate_device_profile() 50 | self.traceparent: str = self.generate_traceparent() 51 | self.generate_pkce_pair() 52 | 53 | self.base_headers: dict = { 54 | "Accept": "application/json", 55 | "Accept-Encoding": "gzip, deflate, br, zstd", 56 | "Accept-Language": "en-US", 57 | "Content-Type": "application/json", 58 | "Content-Length": "1121", 59 | "Device_profile_ref_id": self.device_profile, 60 | "Origin": "https://identity.walmart.com", 61 | "Priority": "u=1, i", 62 | "Referer": None, 63 | "Sec-Ch-Ua": '"Chromium";v="142", "Brave";v="142", "Not_A Brand";v="99"', 64 | "Sec-Ch-Ua-Mobile": "?0", 65 | "Sec-Ch-Ua-Platform": '"Windows"', 66 | "Sec-Fetch-Dest": "empty", 67 | "Sec-Fetch-Mode": "cors", 68 | "Sec-Fetch-Site": "same-origin", 69 | "Sec-Gpc": "1", 70 | "Tenant-Id": None, 71 | "Traceparent": self.traceparent, 72 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36", 73 | "Wm_mp": "true", 74 | "Wm_page_url": None, 75 | "Wm_qos.Correlation_Id": self.correlation_id, 76 | "X-Apollo-Operation-Name": None, 77 | "X-Enable-Server-Timing": "1", 78 | "X-Latency-Trace": "1", 79 | "X-O-Bu": "WALMART-US", 80 | "X-O-Ccm": "server", 81 | "X-O-Correlation-Id": self.correlation_id, 82 | "X-O-Gql-Query": None, 83 | "X-O-Mart": "B2C", 84 | "X-O-Platform": "rweb", 85 | "X-O-Platform-Version": "usweb-1.231.5-a034267af522518f902d66840b28d33cf95a4099-N81117r", 86 | "X-O-Segment": "oaoh" 87 | } 88 | 89 | self.base_payload: dict = { 90 | "query": None, 91 | "variables": { 92 | "input": { 93 | "loginId": self.email, 94 | "loginIdType": "EMAIL", 95 | "ssoOptions": { 96 | "wasConsentCaptured": True, 97 | "callbackUrl": None, 98 | "clientId": None, 99 | "scope": None, 100 | "state": "/?action=SignIn&rm=true", 101 | "challenge": self.code_challenge 102 | } 103 | } 104 | } 105 | } 106 | 107 | self.redirect_uri = None 108 | self.client_id = None 109 | self.tenant_id = None 110 | self.scope = None 111 | 112 | self.isomorphic_session_id = None 113 | self.render_view_id = None 114 | self.trace_id = None 115 | 116 | self.auth_code = None 117 | self.walmart_session: Session = self.login() 118 | 119 | @staticmethod 120 | def generate_device_profile(length: int = 40) -> str: 121 | return "".join(choice(ascii_lowercase + digits) for _ in range(length)) + "-" 122 | 123 | @staticmethod 124 | def generate_correlation_id(length: int = 32) -> str: 125 | return "".join(choice(ascii_letters + digits + "_-") for _ in range(length)) 126 | 127 | @staticmethod 128 | def generate_traceparent() -> str: 129 | return f"00-{token_hex(16)}-{token_hex(8)}-00" 130 | 131 | def generate_pkce_pair(self) -> None: 132 | self.code_verifier = urlsafe_b64encode(token_bytes(32)).decode("utf-8").rstrip("=") 133 | self.code_challenge = urlsafe_b64encode(sha256(self.code_verifier.encode("utf-8")).digest()).decode("utf-8").rstrip("=") 134 | 135 | def extract_oauth_params(self, html_content) -> bool: 136 | match = search(r'"oidcParams"\s*:\s*({[^}]+})', html_content) 137 | 138 | if match: 139 | try: 140 | oidc_params = loads(match.group(1)) 141 | self.client_id: str = oidc_params.get("clientId") 142 | self.scope: str = oidc_params.get("scope") 143 | self.tenant_id: str = oidc_params.get("tenantId") 144 | redirect_uri: str = oidc_params.get("redirectUri") 145 | 146 | self.base_headers["tenant-id"] = self.tenant_id 147 | 148 | if redirect_uri.startswith("/"): 149 | self.redirect_uri: str = f"https://www.walmart.com{redirect_uri}" 150 | self.base_payload["variables"]["input"]["ssoOptions"]["callbackUrl"] = self.redirect_uri 151 | self.base_payload["variables"]["input"]["ssoOptions"]["clientId"] = self.client_id 152 | self.base_payload["variables"]["input"]["ssoOptions"]["scope"] = self.scope 153 | return True 154 | 155 | raise Exception() 156 | 157 | except: 158 | pass 159 | 160 | return False 161 | 162 | def extract_autenticated_oauth_params(self, html_content) -> bool: 163 | try: 164 | isomorphic_match = search(r'"isomorphicSessionId"\s*:\s*"([a-zA-Z0-9_-]+)"', html_content) 165 | if isomorphic_match: 166 | self.isomorphic_session_id = isomorphic_match.group(1) 167 | 168 | else: 169 | raise Exception() 170 | 171 | render_match = search(r'"renderViewId"\s*:\s*"([a-f0-9-]{36})"', html_content) 172 | if render_match: 173 | self.render_view_id = render_match.group(1) 174 | 175 | else: 176 | raise Exception() 177 | 178 | trace_id_match = search(r'"traceId"\s*:\s*"([a-f0-9]{32})"', html_content) 179 | if trace_id_match: 180 | self.trace_id = trace_id_match.group(1) 181 | return True 182 | 183 | raise Exception() 184 | 185 | except: 186 | pass 187 | 188 | return False 189 | 190 | def get_home_webpage(self) -> bool: 191 | try: 192 | response: Response = self.session.get( 193 | "https://www.walmart.com/" 194 | ) 195 | 196 | if response.status_code == 200: 197 | if not self.extract_oauth_params(response.text): 198 | exit() 199 | 200 | return True 201 | 202 | raise Exception() 203 | 204 | except: 205 | pass 206 | 207 | return False 208 | 209 | def get_login_page(self) -> bool: 210 | try: 211 | headers = self.base_headers.copy() 212 | headers["Accept"] = "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8" 213 | headers["Accept-Language"] = "en-US,en;q=0.9" 214 | headers["Priority"] = "u=0, i" 215 | headers["Referer"] = "https://www.walmart.com/" 216 | headers["Sec-Fetch-Dest"] = "document" 217 | headers["Sec-Fetch-Mode"] = "navigate" 218 | headers["Sec-Fetch-Site"] = "same-site" 219 | headers["Sec-Fetch-User"] = "?1" 220 | headers["Upgrade-Insecure-Requests"] = "1" 221 | 222 | params: dict = { 223 | "client_id": self.client_id, 224 | "redirect_uri": self.redirect_uri, 225 | "scope": self.scope, 226 | "tenant_id": self.tenant_id, 227 | "state": "/", 228 | "code_challenge": self.code_challenge 229 | } 230 | 231 | response: Response = self.session.get( 232 | "https://identity.walmart.com/account/login", 233 | headers=headers, 234 | params=params 235 | ) 236 | 237 | if response.status_code == 200: 238 | return True 239 | 240 | raise Exception() 241 | 242 | except: 243 | pass 244 | 245 | return False 246 | 247 | def generate_otp(self) -> bool: 248 | try: 249 | headers = self.base_headers.copy() 250 | headers["Referer"] = f"https://identity.walmart.com/account/signin/withotpchoice?scope={self.scope.replace(' ', '%20')}&redirect_uri={self.redirect_uri}&client_id={self.client_id}&tenant_id={self.tenant_id}&code_challenge={self.code_challenge}&state=%2F%3Faction%3DSignIn%26rm%3Dtrue" 251 | headers["Wm_page_url"] = f"https://identity.walmart.com/account/signin/withotpchoice?scope={self.scope.replace(' ', '%20')}&redirect_uri={self.redirect_uri}&client_id={self.client_id}&tenant_id={self.tenant_id}&code_challenge={self.code_challenge}&state=%2F%3Faction%3DSignIn%26rm%3Dtrue" 252 | headers["X-Apollo-Operation-Name"] = "GenerateOtp" 253 | headers["X-O-Gql-Query"] = "mutation GenerateOtp" 254 | 255 | self.base_payload["query"] = "mutation GenerateOtp($input:GenerateOTPInput!){generateOTP(input:$input){errors{...GenerateOtpErrorFragment}otpResult{...GenerateOtpResultFragment}}}fragment GenerateOtpErrorFragment on IdentityGenerateOTPError{code message}fragment GenerateOtpResultFragment on GenerateOTPResult{receiptId otpOperation otpType otherAccountsWithPhone action{alternateOption currentOption}}" 256 | self.base_payload["variables"]["input"]["otpOperation"] = "OTP_EMAIL_SIGN_IN" 257 | 258 | response: Response = self.session.post( 259 | "https://identity.walmart.com/orchestra/idp/graphql", 260 | headers=headers, 261 | json=self.base_payload 262 | ) 263 | 264 | if response.status_code == 200: 265 | return True 266 | 267 | raise Exception() 268 | 269 | except: 270 | pass 271 | 272 | return False 273 | 274 | def submit_otp(self, otp_code: str) -> bool: 275 | try: 276 | headers = self.base_headers.copy() 277 | headers["Referer"] = f"https://identity.walmart.com/account/signin/otponly?scope={self.scope.replace(' ', '%20')}&redirect_uri={self.redirect_uri}&client_id={self.client_id}&tenant_id={self.tenant_id}&code_challenge={self.code_challenge}&state=%2F%3Faction%3DSignIn%26rm%3Dtrue" 278 | headers["Wm_page_url"] = f"https://identity.walmart.com/account/signin/otponly?scope={self.scope.replace(' ', '%20')}&redirect_uri={self.redirect_uri}&client_id={self.client_id}&tenant_id={self.tenant_id}&code_challenge={self.code_challenge}&state=%2F%3Faction%3DSignIn%26rm%3Dtrue" 279 | headers["X-Apollo-Operation-Name"] = "SignInWithOTP" 280 | headers["X-O-Gql-Query"] = "mutation SignInWithOTP" 281 | 282 | self.base_payload["query"] = "mutation SignInWithOTP( $input:SignInWithOTPInput! $includePhoneInfo:Boolean = false ){signInWithOTP(input:$input){auth{...SignInOtpAuthFragment}authCode{authCode cid}phoneInfo @include(if:$includePhoneInfo){...SignInOtpPhoneInfoFragment}multiFactorInfo{ignoreFactor}errors{...SignInOtpErrorFragment}}}fragment SignInOtpAuthFragment on AuthResult{loginId cid authCode identityToken}fragment SignInOtpPhoneInfoFragment on PhoneInfo{phoneLastFour shouldCollectPhone isEmailSessionTrusted loginId isPhoneSessionTrusted isFirstSession isEmailValidated}fragment SignInOtpErrorFragment on IdentitySignInWithOTPError{code message}" 283 | self.base_payload["variables"]["input"]["otpCode"] = otp_code 284 | self.base_payload["variables"]["input"]["rememberMe"] = True 285 | self.base_payload["variables"]["includePhoneInfo"] = True 286 | 287 | 288 | response: Response = self.session.post( 289 | "https://identity.walmart.com/orchestra/idp/graphql", 290 | headers=headers, 291 | json=self.base_payload 292 | ) 293 | 294 | if response.status_code == 200: 295 | self.auth_code: str = response.json()["data"]["signInWithOTP"]["authCode"]["authCode"] 296 | return True 297 | 298 | raise Exception() 299 | 300 | except: 301 | pass 302 | 303 | return False 304 | 305 | def verify_token(self) -> bool: 306 | try: 307 | self.session.cookies.set( 308 | "walmart-identity-web-code-verifier", 309 | self.code_verifier, 310 | domain=".walmart.com", 311 | path="/" 312 | ) 313 | 314 | params: dict = { 315 | "state": "/", 316 | "client_id": self.client_id, 317 | "redirect_uri": self.redirect_uri, 318 | "scope": self.scope, 319 | "code": self.auth_code, 320 | "action": "SignIn", 321 | "rm": "true" 322 | } 323 | 324 | headers = self.base_headers.copy() 325 | headers["Accept"] = "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8" 326 | headers["Accept-Language"] = "en-US,en;q=0.7" 327 | headers["Priority"] = "u=0, i" 328 | headers["Referer"] = "https://identity.walmart.com/" 329 | headers["Sec-Fetch-Dest"] = "document" 330 | headers["Sec-Fetch-Mode"] = "navigate" 331 | headers["Sec-Fetch-Site"] = "same-site" 332 | headers["Sec-Fetch-User"] = "?1" 333 | headers["Upgrade-Insecure-Requests"] = "1" 334 | 335 | headers.pop("Content-Type", None) 336 | headers.pop("Content-Length", None) 337 | headers.pop("Device_profile_ref_id", None) 338 | headers.pop("Origin", None) 339 | headers.pop("Tenant-Id", None) 340 | headers.pop("Traceparent", None) 341 | headers.pop("Wm_mp", None) 342 | headers.pop("Wm_page_url", None) 343 | headers.pop("Wm_qos.Correlation_Id", None) 344 | headers.pop("X-Apollo-Operation-Name", None) 345 | headers.pop("X-Enable-Server-Timing", None) 346 | headers.pop("X-Latency-Trace", None) 347 | headers.pop("X-O-Bu", None) 348 | headers.pop("X-O-Ccm", None) 349 | headers.pop("X-O-Correlation-Id", None) 350 | headers.pop("X-O-Gql-Query", None) 351 | headers.pop("X-O-Mart", None) 352 | headers.pop("X-O-Platform", None) 353 | headers.pop("X-O-Platform-Version", None) 354 | headers.pop("X-O-Segment", None) 355 | 356 | response: Response = self.session.get( 357 | "https://www.walmart.com/account/verifyToken", 358 | headers=headers, 359 | params=params 360 | ) 361 | 362 | if response.status_code == 200: 363 | return True 364 | 365 | raise Exception() 366 | 367 | except: 368 | pass 369 | 370 | return False 371 | 372 | def get_account_webpage(self) -> bool: 373 | try: 374 | response = self.session.get( 375 | "https://www.walmart.com/account" 376 | ) 377 | 378 | if response.status_code == 200: 379 | if self.extract_autenticated_oauth_params(response.text): 380 | return True 381 | 382 | raise Exception() 383 | 384 | except: 385 | pass 386 | 387 | return False 388 | 389 | def display_name(self) -> bool: 390 | try: 391 | variables = quote(dumps({ 392 | "isCashiLinked": False, 393 | "enableCountryCallingCode": False, 394 | "enablePhoneCollection": False, 395 | "enableMembershipId": False, 396 | "enableMembershipAutoRenew": False, 397 | "enableMembershipQuery": True, 398 | "enableWcp": False, 399 | "enableMembershipAutoRenewalModal": False, 400 | "includeResidencyRegionInfo": False, 401 | "sessionInput": {}, 402 | "includeSessionInfo": False 403 | })) 404 | 405 | headers = self.base_headers.copy() 406 | headers["Baggage"] = f"trafficType=customer,deviceType=desktop,renderScope=CSR,webRequestSource=Browser,pageName=account,isomorphicSessionId={self.isomorphic_session_id},renderViewId={self.render_view_id}" 407 | headers["Referer"] = "https://www.walmart.com/account" 408 | headers["Wm_page_url"] = "https://www.walmart.com/account" 409 | headers["Wm-Client-Traceid"] = token_hex(16) 410 | headers["X-Apollo-Operation-Name"] = "accountLandingPage" 411 | headers["X-O-Gql-Query"] = "query accountLandingPage" 412 | 413 | response: Response = self.session.get( 414 | f"https://www.walmart.com/orchestra/home/graphql/accountLandingPage/781bca8419fd5b5e7a88ff6690e3ce97007469afd2c59b79b44d599218eb4d4d?variables={variables}", 415 | headers=headers 416 | ) 417 | 418 | if response.status_code == 200: 419 | print(f"Logged In As: {response.json()['data']['account']['profile']['firstName']}") 420 | return True 421 | 422 | raise Exception() 423 | 424 | except: 425 | pass 426 | 427 | return False 428 | 429 | def login(self) -> Session: 430 | self.get_home_webpage() 431 | self.get_login_page() 432 | self.generate_otp() 433 | 434 | code: str = mail_connection.MailConnection(self.server, self.port, self.username, self.password).fetch_otp() 435 | self.submit_otp(code) 436 | self.verify_token() 437 | self.get_account_webpage() 438 | self.display_name() 439 | 440 | return self.session 441 | 442 | auth = WalmartSession() 443 | --------------------------------------------------------------------------------