├── README.md ├── tasks.json ├── package.json ├── .gitignore └── app.js /README.md: -------------------------------------------------------------------------------- 1 | # off---white 2 | 3 | Off white bot (December 2018 -> January 2020). RIP Off---white. 4 | 5 | ### Credit 6 | 7 | Twitter - @notchefbob 8 | Twitter - @unreleased 9 | -------------------------------------------------------------------------------- /tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "client": { 3 | "webhook": "", 4 | "api2Captcha": "" 5 | }, 6 | "users": [{ 7 | "email": "", 8 | "password": "", 9 | "card": { 10 | "number": "", 11 | "cvv": "", 12 | "exp_month": "", 13 | "exp_year": "" 14 | }, 15 | "proxy": "" 16 | }] 17 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "off-white", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "2captcha": "^1.0.11", 13 | "async": "^2.6.1", 14 | "cheerio": "^1.0.0-rc.2", 15 | "discord.js": "^11.4.2", 16 | "fs": "0.0.1-security", 17 | "request": "^2.88.0", 18 | "request-promise": "^4.2.2" 19 | } 20 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | const Request = require('request-promise'); 2 | const cheerio = require('cheerio'); 3 | const fs = require('fs'); 4 | const Discord = require('discord.js'); 5 | const async = require('async'); 6 | 7 | 8 | const SHOP_URL = 'www.off---white.com' 9 | 10 | // User 11 | var tasks = fs.readFileSync('tasks.json', 'utf8'); 12 | tasks = JSON.parse(tasks); 13 | 14 | 15 | 16 | var wh = tasks.client.webhook; 17 | wh = wh.split('/'); 18 | 19 | const webhook = new Discord.WebhookClient(wh[5], wh[6]) 20 | 21 | 22 | 23 | class Instance { 24 | 25 | constructor(user) { 26 | this.user = user; 27 | this.user.billing = {}; 28 | this.user.order = {}; 29 | this.user.payment = {}; 30 | 31 | // Riskified Object 32 | this.rx = { 33 | beacon : this.getRiskifiedBeacon(), 34 | cookie : this.getRiskifiedCookieId(), 35 | timezone : this.getRiskifiedTimezone(), 36 | lowestTime : false, 37 | href : 'https://www.off---white.com/en/GB/', 38 | referrer : '', 39 | page : '', 40 | timestamp : '', 41 | }; 42 | 43 | // Variti Object 44 | this.variti = { 45 | redirect: '' 46 | } 47 | 48 | // User Agent 49 | this.userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36' 50 | 51 | // Max spend limit 52 | this.maxOrderAmount = 350 53 | 54 | // Prevent global cookie jar 55 | this.jar = Request.jar(); 56 | 57 | // Set instance request 58 | this.request = Request.defaults({ 59 | jar : this.jar, 60 | proxy : this.user.proxy, // change false 61 | simple : false, 62 | method : 'GET', 63 | forever : true, 64 | followRedirect : false, 65 | followAllRedirects : false, 66 | resolveWithFullResponse : true, 67 | gzip : true 68 | }) 69 | 70 | this.timer = { 71 | start: 0, 72 | end : 0 73 | } 74 | 75 | this.start(); 76 | // this.populateCart(); 77 | } 78 | 79 | async start() { 80 | 81 | // Get Variti cookies 82 | await this.findVariti(); 83 | await this.visitVariti(); 84 | await this.solveVariti(); 85 | await this.completeVariti(); 86 | 87 | // Login user 88 | await this.login(); 89 | 90 | 91 | // Get CSRF token 92 | this.csrf = await this.getCSRF() 93 | while (!this.csrf) { 94 | await wait(300); 95 | this.csrf = await this.getCSRF(); 96 | } 97 | 98 | // Solve riskified after logging in. 99 | this.rx.href = "https://www.off---white.com/en/GB" 100 | await this.solveRiskified(); 101 | 102 | 103 | // Add and remove an item from your cart to generate a valid guest token. 104 | // this.populateCart(); 105 | 106 | // Get product from off white API 107 | var monitor = await this.getRestock(); 108 | while (!monitor) { 109 | await wait(300); 110 | monitor = await this.getRestock(); 111 | } 112 | 113 | 114 | // Cart item 115 | this.timer.start = ~~(new Date().getTime() / 1000) 116 | await this.cart(monitor); 117 | 118 | // Get billing 119 | var address_ids = await this.getBilling(); 120 | if (!address_ids) { 121 | return this.ts(`There was an error getting your billing details. This is most-likely a carting issue`) 122 | } 123 | 124 | // Submit billing 125 | await this.billing(); 126 | 127 | // Get delivery 128 | await this.delivery(); 129 | 130 | /** Get everything from Billing, skip delivery. */ 131 | 132 | if (this.order.amount < this.maxOrderAmount) { 133 | await this.getToken(); 134 | await this.getPaymentFrame(); 135 | await this.pay(); 136 | await this.process() 137 | this.timer.end = ~~(new Date().getTime() / 1000) 138 | await this.getOrder() 139 | return 140 | } else { 141 | this.ts(`You couldn't complete order, The price is above the set limit of ${this.maxOrderAmount} EUR`); 142 | } 143 | 144 | } 145 | 146 | async findVariti() { 147 | var opts = { 148 | url: "https://www.off---white.com", 149 | method: "GET", 150 | proxy: this.user.proxy, 151 | headers: { 152 | 'Host' : "www.off---white.com", 153 | 'Connection' : 'keep-alive', 154 | 'Pragma' : 'no-cache', 155 | 'Cache-Control' : 'no-cache', 156 | 'Upgrade-Insecure-Requests': '1', 157 | 'User-Agent' : this.userAgent, 158 | 'Accept' : 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 159 | 'Accept-Encoding' : 'gzip', 160 | 'Accept-Language' : 'en-GB,en-US;q=0.9,en;q=0.8', 161 | } 162 | } 163 | 164 | var resp = this.request.get(opts).then((res) => { 165 | if (res.headers.location != undefined) { 166 | this.variti.redirect = res.headers.location 167 | this.ts(`Variti redirect found`); 168 | return true; 169 | } else { 170 | return false; 171 | } 172 | }).catch((err) => { 173 | console.log(err); 174 | return false; 175 | }) 176 | 177 | return resp; 178 | } 179 | 180 | async visitVariti() { 181 | var opts = { 182 | url: this.variti.redirect, 183 | method: "GET", 184 | proxy: false, 185 | headers: { 186 | 'Host' : 'ohio8.vchecks.me', 187 | 'Connection' : 'keep-alive', 188 | 'Pragma' : 'no-cache', 189 | 'Cache-Control' : 'no-cache', 190 | 'Upgrade-Insecure-Requests': '1', 191 | 'User-Agent' : this.userAgent, 192 | 'Accept' : 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 193 | 'Accept-Encoding' : 'gzip', 194 | 'Accept-Language' : 'en-GB,en-US;q=0.9,en;q=0.8', 195 | } 196 | } 197 | 198 | this.ts(`Attempting to solve Variti...`) 199 | 200 | var resp = this.request(opts).then((res) => { 201 | if (res.statusCode === 307) { 202 | this.variti.redirect = res.headers.location 203 | this.ts("Succesfully visited Variti and got second redirect") 204 | return true; 205 | } else { 206 | this.ts("Failed to find Variti redirect") 207 | return false; 208 | } 209 | 210 | }); 211 | 212 | return resp; 213 | } 214 | 215 | async solveVariti() { 216 | var opts = { 217 | url: this.variti.redirect, 218 | proxy: this.user.proxy, 219 | headers: { 220 | 'Host' : 'www.off---white.com', 221 | 'Connection' : 'keep-alive', 222 | 'Pragma' : 'no-cache', 223 | 'Cache-Control' : 'no-cache', 224 | 'Upgrade-Insecure-Requests': '1', 225 | 'User-Agent' : this.userAgent, 226 | 'Accept' : 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 227 | 'Accept-Encoding' : 'gzip', 228 | 'Accept-Language' : 'en-GB,en-US;q=0.9,en;q=0.8', 229 | } 230 | } 231 | 232 | var resp = this.request(opts).then((res) => { 233 | 234 | if (res.statusCode == 307) { 235 | this.ts('Finishing Variti solution.') 236 | } else { 237 | this.ts('[SOLVEVARITI] Variti hit an error') 238 | console.log(res.headers.location); 239 | console.log(res.body) 240 | console.log(res.statusCode) 241 | } 242 | 243 | 244 | 245 | return true; 246 | }) 247 | 248 | return resp; 249 | } 250 | 251 | async completeVariti() { 252 | var opts = { 253 | url: 'https://www.off---white.com/en/GB/cart.json', 254 | headers: { 255 | 'Host' : 'www.off---white.com', 256 | 'Connection' : 'keep-alive', 257 | 'Pragma' : 'no-cache', 258 | 'Cache-Control' : 'no-cache', 259 | 'Upgrade-Insecure-Requests': '1', 260 | 'User-Agent' : this.userAgent, 261 | 'Accept' : 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 262 | 'Accept-Encoding' : 'gzip', 263 | 'Accept-Language' : 'en-GB,en-US;q=0.9,en;q=0.8', 264 | } 265 | } 266 | 267 | var resp = this.request(opts).then((res) => { 268 | 269 | if (res.statusCode === 200) { 270 | this.ts(`Variti solved successfully.`) 271 | } else { 272 | this.ts(`Variti failed.`) 273 | console.log(res.headers.location); 274 | console.log(res.body) 275 | console.log(res.statusCode) 276 | } 277 | 278 | 279 | 280 | return true; 281 | }) 282 | 283 | return resp; 284 | } 285 | 286 | async login() { 287 | var opts = { 288 | url: 'https://www.off---white.com/en/GB/login', 289 | method: 'POST', 290 | headers: { 291 | 'Host' : 'www.off---white.com', 292 | 'Connection' : 'keep-alive', 293 | 'Pragma' : 'no-cache', 294 | 'Cache-Control' : 'no-cache', 295 | 'Upgrade-Insecure-Requests': '1', 296 | 'User-Agent' : this.userAgent, 297 | 'Accept' : 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 298 | 'Accept-Encoding' : 'gzip', 299 | 'Accept-Language' : 'en-GB,en-US;q=0.9,en;q=0.8', 300 | }, 301 | form: { 302 | spree_user: { 303 | email : this.user.email, 304 | password : this.user.password 305 | } 306 | } 307 | } 308 | 309 | this.ts(`Logging in: ${this.user.email}`); 310 | var resp = this.request(opts).then((res) => { 311 | if (res.headers.location === undefined) { 312 | if (res.statusCode == 403) { 313 | this.ts(`Couldn't login because this proxy is banned.`) 314 | } else { 315 | this.ts(`Invalid email or password`) 316 | } 317 | 318 | return false; 319 | } 320 | 321 | return this.ts(`Successfully logged in: ${this.user.email}`); 322 | 323 | }).catch((err) => { 324 | console.log(err); 325 | return this.ts("[LOGIN] An error occurred!"); 326 | }); 327 | 328 | return resp; 329 | } 330 | 331 | async getRestock() { 332 | var opts = { 333 | url : 'https://notify.express/middleware/off-white/?token=9y43rWeVEGYuW2QdP6Ab58akChd', 334 | timeout : 1000, 335 | json : true, 336 | proxy : false 337 | } 338 | 339 | var resp = this.request(opts).then((res) => { 340 | 341 | console.log(res.body); 342 | 343 | this.ts(`Product status: ${res.body.available}`) 344 | 345 | // testing 346 | // return 111490 347 | 348 | if (res.body.available) { 349 | this.ts(`Product found instock: ${res.body.variant_id}`) 350 | return res.body.variant_id 351 | } else { 352 | /* this.ts(`[DEBUG MODE] Forcing variant`) 353 | this.ts(`[DEBUG MODE] Forcing variant`) 354 | this.ts(`[DEBUG MODE] Forcing variant`) 355 | this.ts(`[DEBUG MODE] Forcing variant`) 356 | this.ts(`[DEBUG MODE] Forcing variant`) 357 | this.ts(`[DEBUG MODE] Forcing variant`) 358 | this.ts(`[DEBUG MODE] Forcing variant`) */ 359 | return false 360 | } 361 | 362 | }).catch((err) => { 363 | this.ts("[GETRESTOCK] An error occurred!"); 364 | return false 365 | }); 366 | 367 | return resp; 368 | } 369 | 370 | async cart(variant) { 371 | var opts = { 372 | url : `https://www.off---white.com/en/GB/frame_increment_item_quantity_from_cart?variant_id=${variant}`, 373 | method : 'HEAD' 374 | } 375 | 376 | this.ts(`Started`) 377 | var resp = this.request(opts).then((res) => { 378 | return this.ts("Carted!"); 379 | }).catch((err) => { 380 | return this.ts("[CART] An error occurred!"); 381 | }); 382 | 383 | return resp; 384 | // 500 Internal Server Error 385 | } 386 | 387 | async getCSRF() { 388 | var opts = { 389 | url: 'https://www.off---white.com/admin/authorization_failure', 390 | headers: { 391 | 'Host' : 'www.off---white.com', 392 | 'Connection' : 'keep-alive', 393 | 'Pragma' : 'no-cache', 394 | 'Cache-Control' : 'no-cache', 395 | 'Upgrade-Insecure-Requests': '1', 396 | 'User-Agent' : this.userAgent, 397 | 'Accept' : 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 398 | 'Accept-Encoding' : 'gzip', 399 | 'Accept-Language' : 'en-GB,en-US;q=0.9,en;q=0.8' 400 | } 401 | } 402 | 403 | this.ts('Getting CSRF token for future requests.') 404 | 405 | var resp = this.request(opts).then((res) => { 406 | 407 | var $ = cheerio.load(res.body) 408 | 409 | if ($("meta[name='csrf-token']").length > 0) { 410 | var token = $("meta[name='csrf-token']").attr("content") 411 | this.ts(`Token found: ${token}`); 412 | } else { 413 | view(res.body); 414 | this.ts(res.statusCode); 415 | 416 | this.ts(`Failed to find token. Retrying...`) 417 | return false; 418 | } 419 | 420 | return token; 421 | 422 | }).catch((err) => { 423 | console.log(err); 424 | return this.ts("[GetCSRF] An error occurred!"); 425 | }); 426 | 427 | return resp; 428 | } 429 | 430 | async getBilling() { 431 | var opts = { 432 | url: 'https://www.off---white.com/en/GB/checkout/address', 433 | headers: { 434 | 'Host' : 'www.off---white.com', 435 | 'Connection' : 'keep-alive', 436 | 'Pragma' : 'no-cache', 437 | 'Cache-Control' : 'no-cache', 438 | 'Upgrade-Insecure-Requests': '1', 439 | 'User-Agent' : this.userAgent, 440 | 'Accept' : 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 441 | 'Accept-Encoding' : 'gzip', 442 | 'Accept-Language' : 'en-GB,en-US;q=0.9,en;q=0.8' 443 | } 444 | } 445 | 446 | var resp = this.request(opts).then(async (res) => { 447 | 448 | if (res.headers.location !== undefined) { 449 | return false; 450 | } 451 | 452 | var $ = cheerio.load(res.body); 453 | 454 | this.user.billing = { 455 | firstname : $("#order_bill_address_attributes_firstname").val(), 456 | lastname : $("#order_bill_address_attributes_lastname").val(), 457 | address1 : $("#order_bill_address_attributes_address1").val(), 458 | address2 : $("#order_bill_address_attributes_address2").val(), 459 | city : $("#order_bill_address_attributes_city").val(), 460 | country_id : $("#order_bill_address_attributes_country_id").val(), 461 | state_id : $("#order_bill_address_attributes_state_id").val(), 462 | state_name : $("#order_bill_address_attributes_state_name").val(), 463 | zipcode : $("#order_bill_address_attributes_zipcode").val(), 464 | phone : $("#order_bill_address_attributes_phone").val(), 465 | hs_fiscal_code : $("#order_bill_address_attributes_hs_fiscal_code").val() || '', 466 | } 467 | 468 | this.order = { 469 | billing_id : $("#order_bill_address_attributes_id").val(), 470 | shipping_id : $("#order_ship_address_attributes_id").val(), 471 | state_lock_version : $("#order_state_lock_version").val(), 472 | } 473 | 474 | this.ts(`[DEBUG] Current URL: ${res.request.uri.href}`) 475 | this.rx.href = res.request.uri.href 476 | await this.solveRiskified(); 477 | 478 | return true; 479 | 480 | }).catch((err) => { 481 | this.ts("[GETBILLING] An error occurred!"); 482 | return false; 483 | }); 484 | 485 | return resp; 486 | } 487 | 488 | async billing() { 489 | var opts = { 490 | url : 'https://www.off---white.com/en/GB/checkout/update/address', 491 | method : 'POST', 492 | followAllRedirects : true, 493 | followRedirect : true, 494 | headers: { 495 | 'Host' : 'www.off---white.com', 496 | 'Connection' : 'keep-alive', 497 | 'Pragma' : 'no-cache', 498 | 'Cache-Control' : 'no-cache', 499 | 'Upgrade-Insecure-Requests': '1', 500 | 'User-Agent' : this.userAgent, 501 | 'Accept' : 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 502 | 'Accept-Encoding' : 'gzip', 503 | 'Accept-Language' : 'en-GB,en-US;q=0.9,en;q=0.8' 504 | }, 505 | form: { 506 | authenticity_token : this.csrf, 507 | _method : 'PATCH', 508 | terms_and_conditions : 'yes', 509 | order: { 510 | email : this.user.email, 511 | state_lock_version : this.order.state_lock_version, 512 | use_billing : '1', 513 | save_user_address : '1', 514 | bill_address_attributes: Object.assign(this.user.billing, {id: this.order.billing_id}), 515 | ship_address_attributes: { 516 | id: this.order.shipping_id 517 | } 518 | } 519 | } 520 | } 521 | 522 | this.ts(`Submitting billing details...`) 523 | var resp = this.request(opts).then(async (res) => { 524 | 525 | var $ = cheerio.load(res.body) 526 | 527 | this.ts("Got delivery information!"); 528 | 529 | this.order = Object.assign(this.order, { 530 | shipping_id : $('.shipping-method input').val(), 531 | shipment_id : $('#order_shipments_attributes_0_id').val(), 532 | amount : $(".amount").eq(1).text().replace(/,/g, '').substring(2), 533 | state_lock_version : $("#order_state_lock_version").val(), 534 | }) 535 | 536 | this.ts(`[DEBUG] Current URL: ${res.request.uri.href}`) 537 | this.rx.referrer = this.rx.href 538 | this.rx.href = res.request.uri.href 539 | await this.solveRiskified(); 540 | return true; 541 | }).catch((err) => { 542 | return this.ts("[BILLING] An error occurred!"); 543 | }); 544 | 545 | return resp; 546 | } 547 | 548 | async delivery() { 549 | var opts = { 550 | url : 'https://www.off---white.com/en/GB/checkout/update/delivery', 551 | method : 'POST', 552 | followRedirect : true, 553 | followAllRedirects : true, 554 | headers: { 555 | 'Host' : 'www.off---white.com', 556 | 'Connection' : 'keep-alive', 557 | 'Pragma' : 'no-cache', 558 | 'Cache-Control' : 'no-cache', 559 | 'Upgrade-Insecure-Requests': '1', 560 | 'User-Agent' : this.userAgent, 561 | 'Accept' : 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 562 | 'Accept-Encoding' : 'gzip', 563 | 'Accept-Language' : 'en-GB,en-US;q=0.9,en;q=0.8' 564 | }, 565 | form: { 566 | utf8 : '✓', 567 | _method : 'PATCH', 568 | authenticity_token : this.csrf, 569 | order: { 570 | state_lock_version : this.order.state_lock_version, 571 | shipments_attributes: [{ 572 | selected_shipping_rate_id : this.order.shipping_id, 573 | id : this.order.shipment_id 574 | }] 575 | } 576 | } 577 | } 578 | 579 | 580 | var resp = this.request(opts).then(async (res) => { 581 | var $ = cheerio.load(res.body) 582 | this.ts("Delivery submitted!"); 583 | this.ts(`[DEBUG] Current URL: ${res.request.uri.href}`) 584 | this.order.amount = $(".gestpay-data").attr("data-amount") 585 | this.order.transaction = $(".gestpay-data").attr("data-transaction") 586 | this.rx.referrer = this.rx.href 587 | this.rx.href = res.request.uri.href 588 | await this.solveRiskified(); 589 | 590 | return true; 591 | }).catch((err) => { 592 | console.log(err) 593 | return this.ts("[DELIVERY] An error occurred!"); 594 | }); 595 | 596 | return resp; 597 | 598 | } 599 | 600 | async getToken() { 601 | var opts = { 602 | url : 'https://www.off---white.com/en/GB/checkout/payment/get_token.json', 603 | method : 'POST', 604 | json : true, 605 | headers: { 606 | 'Host' : 'www.off---white.com', 607 | 'Connection' : 'keep-alive', 608 | 'Pragma' : 'no-cache', 609 | 'Cache-Control' : 'no-cache', 610 | 'Upgrade-Insecure-Requests': '1', 611 | 'User-Agent' : this.userAgent, 612 | 'Accept' : 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 613 | 'Accept-Encoding' : 'gzip', 614 | 'Accept-Language' : 'en-GB,en-US;q=0.9,en;q=0.8', 615 | 'X-CSRF-Token' : this.csrf 616 | }, 617 | form: { 618 | transaction : this.order.transaction, 619 | amount : this.order.amount, 620 | beacon_session_id : this.rx.beacon 621 | } 622 | } 623 | 624 | this.ts(`Attempting to get payment information`); 625 | 626 | var resp = this.request(opts).then((res) => { 627 | this.ts(`Got payment token: ${res.body.token}`); 628 | this.user.payment.payment_token = res.body.token; 629 | return true; 630 | }).catch((err) => { 631 | console.log(err) 632 | return this.ts("[GETTOKEN] An error occurred!"); 633 | }); 634 | 635 | return resp; 636 | } 637 | 638 | async getPaymentFrame() { 639 | var opts = { 640 | url: `https://ecomm.sella.it/Pagam/hiddenIframe.aspx?a=9091712&b=${this.user.payment.payment_token}&MerchantUrl=https%3a%2f%2fwww.off---white.com%2fen%2fGB%2fcheckout%2fpayment`, 641 | headers: { 642 | 'Host' : 'www.off---white.com', 643 | 'Connection' : 'keep-alive', 644 | 'Pragma' : 'no-cache', 645 | 'Cache-Control' : 'no-cache', 646 | 'Upgrade-Insecure-Requests': '1', 647 | 'User-Agent' : this.userAgent, 648 | 'Accept' : 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 649 | 'Accept-Encoding' : 'gzip', 650 | 'Accept-Language' : 'en-GB,en-US;q=0.9,en;q=0.8' 651 | } 652 | } 653 | 654 | var resp = this.request(opts).then((res) => { 655 | 656 | var $ = cheerio.load(res.body); 657 | 658 | this.gestpay = { 659 | __VIEWSTATE : $("#__VIEWSTATE").val(), 660 | __VIEWSTATEGENERATOR : $("#__VIEWSTATEGENERATOR").val(), 661 | __EVENTVALIDATION : $("#__EVENTVALIDATION").val(), 662 | cardnumber : this.user.card.number.split(' ').join(''), 663 | cardExpiryMonth : this.user.card.exp_month, 664 | cardExpiryYear : this.user.card.exp_year, 665 | cvv : this.user.card.cvv, // 899 666 | buyerName : undefined, 667 | buyerEMail : undefined, 668 | inputString : this.user.payment.payment_token, 669 | pares : '', 670 | logPostData : '', 671 | shopLogin : '' 672 | } 673 | 674 | this.ts("Got gestpay payment data"); 675 | return true; 676 | }).catch((err) => { 677 | this.ts("[GETPAYMENTFRAME] An error occurred!"); 678 | return false; 679 | }); 680 | 681 | return resp; 682 | } 683 | 684 | async pay() { 685 | var opts = { 686 | url: `https://ecomm.sella.it/Pagam/hiddenIframe.aspx?a=9091712&b=${this.user.payment.payment_token}&MerchantUrl=https%3a%2f%2fwww.off---white.com%2fen%2fGB%2fcheckout%2fpayment`, 687 | method: 'POST', 688 | headers: { 689 | 'Host' : 'www.off---white.com', 690 | 'Connection' : 'keep-alive', 691 | 'Pragma' : 'no-cache', 692 | 'Cache-Control' : 'no-cache', 693 | 'Upgrade-Insecure-Requests': '1', 694 | 'User-Agent' : this.userAgent, 695 | 'Accept' : 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 696 | 'Accept-Encoding' : 'gzip', 697 | 'Accept-Language' : 'en-GB,en-US;q=0.9,en;q=0.8' 698 | }, 699 | form: this.gestpay 700 | } 701 | 702 | var resp = this.request(opts).then((res) => { 703 | 704 | var $ = cheerio.load(res.body) 705 | 706 | var unformattedToken = $("#form1 > script").html().split("')//]]>") 707 | this.user.payment.process_token = unformattedToken[0].split("delayedSendResult('0','','','','")[1] 708 | 709 | this.ts(`Getting payment success token: ${this.user.payment.process_token}`) 710 | 711 | return true; 712 | 713 | }).catch((err) => { 714 | console.log(err); 715 | this.ts("[PAY] An error occurred!"); 716 | return false; 717 | }); 718 | 719 | return resp; 720 | 721 | } 722 | 723 | async process() { 724 | var opts = { 725 | url : 'https://www.off---white.com/checkout/payment/process_token.json', 726 | method : 'POST', 727 | headers: { 728 | 'Host' : 'www.off---white.com', 729 | 'Connection' : 'keep-alive', 730 | 'Pragma' : 'no-cache', 731 | 'Cache-Control' : 'no-cache', 732 | 'Upgrade-Insecure-Requests': '1', 733 | 'User-Agent' : this.userAgent, 734 | 'Accept' : 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 735 | 'Accept-Encoding' : 'gzip', 736 | 'Accept-Language' : 'en-GB,en-US;q=0.9,en;q=0.8', 737 | 'X-CSRF-Token': this.csrf 738 | }, 739 | form: { 740 | token: this.user.payment.process_token 741 | } 742 | } 743 | 744 | this.ts("Processing token...") 745 | var resp = this.request(opts).then((res) => { 746 | this.ts("Token processed"); 747 | return this.ts(res.body); 748 | }).catch((err) => { 749 | console.log(err); 750 | this.ts("[PROCESS] An error occurred!"); 751 | return false; 752 | }); 753 | 754 | return resp; 755 | } 756 | 757 | async getOrder() { 758 | var opts = { 759 | url: `https://www.off---white.com/en/GB/orders/${this.order.transaction}`, 760 | headers: { 761 | 'Host' : 'www.off---white.com', 762 | 'Connection' : 'keep-alive', 763 | 'Pragma' : 'no-cache', 764 | 'Cache-Control' : 'no-cache', 765 | 'Upgrade-Insecure-Requests': '1', 766 | 'User-Agent' : this.userAgent, 767 | 'Accept' : 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 768 | 'Accept-Encoding' : 'gzip', 769 | 'Accept-Language' : 'en-GB,en-US;q=0.9,en;q=0.8' 770 | } 771 | } 772 | 773 | var resp = this.request(opts).then((res) => { 774 | 775 | var $ = cheerio.load(res.body); 776 | 777 | var thumbnail = $("table img").attr("src"), 778 | product = $("table img").attr("alt"), 779 | time = `00:${this.timer.end - this.timer.start}`, 780 | size = $(".descItem").text().match(/\(([^)]+)\)/)[1] 781 | 782 | 783 | success_discord_webhook(this.order.transaction, product, this.user.email, time, thumbnail, size) 784 | 785 | }).catch((err) => { 786 | console.log(err); 787 | this.ts("[GETORDER] An error occurred!"); 788 | return false 789 | }); 790 | 791 | return resp; 792 | } 793 | 794 | async harvestRecaptcha() { 795 | 796 | 797 | var opts = { 798 | url : 'http://2captcha.com/in.php', 799 | method : 'POST', 800 | json : true, 801 | form: { 802 | method : 'userrecaptcha', 803 | googlekey : '6LfRAXkUAAAAAMJTybeTyJcGpiXI1o-H3KEAXM2w', 804 | pageurl : 'https://www.off---white.com/en/GB/men/products/omia002f180340041000', 805 | key : tasks.client.api2Captcha, 806 | json : true, 807 | } 808 | } 809 | 810 | this.ts(`Starting harvester to create guest order.`) 811 | 812 | var resp = this.request(opts).then(async (res) => { 813 | 814 | var captchaResponse = await this.getCaptchaResponse(res.body.request); 815 | while (!captchaResponse) { 816 | await wait(5000); 817 | captchaResponse = await this.getCaptchaResponse(res.body.request); 818 | console.log(captchaResponse); 819 | } 820 | 821 | return captchaResponse; 822 | }).catch((err) => { 823 | console.log(err); 824 | this.ts("[POPULATECART POST] ERROR"); 825 | }) 826 | 827 | return resp; 828 | } 829 | 830 | async getCaptchaResponse(id) { 831 | 832 | var getOpts = { 833 | url : `http://2captcha.com/res.php?id=${id}&key=${tasks.client.api2Captcha}&action=get&json=true`, 834 | json : true 835 | } 836 | 837 | var resp = this.request(getOpts).then((res) => { 838 | 839 | if (res.body.status) { 840 | this.ts(`Captcha harvested successfully. Generating order.`) 841 | return res.body.request 842 | } 843 | 844 | this.ts(`Captcha not harvested yet.`) 845 | return false; 846 | 847 | 848 | }).catch((err) => { 849 | this.ts("[POPULATECART GET] ERROR"); 850 | }) 851 | 852 | return resp; 853 | } 854 | 855 | async populateCart() { 856 | var postOpts = { 857 | url : 'https://www.off---white.com/en/GB/orders/populate.json', 858 | method : 'POST', 859 | json : true, 860 | headers: { 861 | 'Host' : 'www.off---white.com', 862 | 'Connection' : 'keep-alive', 863 | 'Pragma' : 'no-cache', 864 | 'Cache-Control' : 'no-cache', 865 | 'Upgrade-Insecure-Requests': '1', 866 | 'User-Agent' : this.userAgent, 867 | 'Accept' : 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 868 | 'Accept-Encoding' : 'gzip', 869 | 'Accept-Language' : 'en-GB,en-US;q=0.9,en;q=0.8', 870 | 'Content-type': 'application/json', 871 | 'X-CSRF-Token': this.csrf 872 | }, 873 | body: { 874 | //"g-recaptcha-response": await this.harvestRecaptcha(), 875 | "quantity": 0, 876 | "variant_id": 107443 877 | } 878 | } 879 | 880 | this.request(postOpts).then((res) => { 881 | if (res.statusCode == "200") { 882 | this.ts(`Order successfully created. Removing the item we carted.`); 883 | } else { 884 | this.ts(`Error carting the product`); 885 | } 886 | }).catch((err) => { 887 | console.log(err); 888 | this.ts("[POPULATECART SUBMIT] ERROR"); 889 | }) 890 | } 891 | 892 | 893 | getRiskifiedTimestamp() { 894 | var date = Math.floor(new Date().getTime()) 895 | var longTs = Math.random() * 1e16; 896 | date = `${date}0.${longTs}` 897 | 898 | return date 899 | } 900 | 901 | getRiskifiedBeacon() { 902 | var uid = []; 903 | for (var i = 0; i < 6; i++) uid.push(Math.random().toString(16).substr(2,8)); 904 | return uid.join('-'); 905 | } 906 | 907 | getRiskifiedCookieId() { 908 | "use strict"; 909 | return Math.random().toString(36).substr(2, 15) + Math.random().toString(36).substr(2, 15) 910 | } 911 | 912 | getRiskifiedPageId() { 913 | "use strict"; 914 | return Math.random().toString(36).substr(3, 6) 915 | } 916 | 917 | getRiskifiedTimezone() { 918 | "use strict"; 919 | return JSON.parse(JSON.stringify(-1 * (new Date).getTimezoneOffset())) 920 | } 921 | 922 | //https://beacon.riskified.com/?shop=www.off---white.com&sid= 923 | 924 | 925 | async riskifiedBeaconRequest() { 926 | var url = `https://beacon.riskified.com/` 927 | url += `?shop=${SHOP_URL}` 928 | url += `&sid=${this.rx.beacon}` 929 | 930 | var opts = { 931 | url : url, 932 | method : 'GET' 933 | } 934 | 935 | var resp = this.request(opts).then((res) => { 936 | var unformattedToken = res.body.split('function getYyRxId3() { return "'); 937 | var token = unformattedToken[1].split('";}')[0]; 938 | if (token.length > 0) { 939 | return token; 940 | } 941 | return false; 942 | }).catch((err) => { 943 | console.log(err); 944 | this.ts("[RISKIFIEDIMAGEREQUEST] An error occurred!"); 945 | return false; 946 | }); 947 | return resp; 948 | } 949 | 950 | async riskifiedImageRequest() { 951 | var url = `https://img.riskified.com/img/image-l.gif` 952 | url += `?t=${this.getRiskifiedTimestamp()}` 953 | url += `&c=${this.rx.cookie}`, 954 | url += `&p=${this.rx.page}` 955 | url += `&a=${this.rx.beacon}` 956 | url += `&o=${SHOP_URL}` 957 | url += `&rt=${this.rx.timestamp}` 958 | 959 | var opts = { 960 | url : url, 961 | method : 'GET' 962 | } 963 | 964 | var startTime = Date.now() 965 | var resp = this.request(opts).then((res) => { 966 | var timeElapsed = Date.now() - startTime 967 | this.ts(`[DEBUG] Image request completed in: ${timeElapsed}`) 968 | if (!this.rx.lowestTime) { 969 | this.rx.lowestTime = timeElapsed 970 | } else if (timeElapsed < this.rx.lowestTime) { 971 | this.rx.lowestTime = timeElapsed 972 | } 973 | return true; 974 | }).catch((err) => { 975 | console.log(err); 976 | this.ts("[RISKIFIEDIMAGEREQUEST] An error occurred!"); 977 | return false; 978 | }); 979 | 980 | return resp; 981 | } 982 | 983 | async solveRiskified() { 984 | this.rx.timestamp = await this.riskifiedBeaconRequest(); 985 | this.rx.page = this.getRiskifiedPageId(); 986 | var imageRequestsComplete = true 987 | 988 | for (var i = 0; i < 6; i++) { 989 | var imageResponse = await this.riskifiedImageRequest() 990 | if (!imageResponse) { 991 | this.ts("[RISKIFIED] An error occured while issuing image requests") 992 | imageRequestsComplete = false 993 | break 994 | } else if (i == 5) { 995 | this.ts("Image requests completed") 996 | } 997 | } 998 | 999 | var url = `https://c.riskified.com/client_infos.json` 1000 | url += `?lat=${this.rx.lowestTime}` 1001 | url += `&timezone=${this.rx.timezone}` 1002 | url += `×tamp=${this.rx.timestamp}` 1003 | url += `&cart_id=${this.rx.beacon}` 1004 | url += `&shop_id=${SHOP_URL}` 1005 | url += `&referrer=${this.rx.referrer}` 1006 | url += `&href=${this.rx.href}` 1007 | url += `&riskified_cookie=${this.rx.cookie}` 1008 | url += `&color_depth=24` 1009 | url += `&page=${this.rx.page}` 1010 | url += `&shop=${SHOP_URL}` 1011 | url += `&hardware_concurrency=8` 1012 | url += `&has_touch=false` 1013 | url += `&debug_print=false` 1014 | url += `&console_error=console.memory is undefined` 1015 | url += `&battery_error=Error getBattery()` 1016 | url += `&initial_cookie_state_0=http` 1017 | url += `&initial_cookie_state_1=local` 1018 | url += `&initial_cookie_state_2=session` 1019 | 1020 | var opts = { 1021 | url : url, 1022 | method : 'GET' 1023 | } 1024 | 1025 | var resp = this.request(opts).then((res) => { 1026 | if (imageRequestsComplete) { 1027 | this.rx.lowestTime = false 1028 | return this.ts("Riskified Completed"); 1029 | } else { 1030 | return this.ts("Riskified Partially Completed"); 1031 | } 1032 | 1033 | }).catch((err) => { 1034 | console.log(err); 1035 | this.ts("[RISKIFIED] An error occurred!"); 1036 | return false; 1037 | }); 1038 | 1039 | return resp 1040 | 1041 | } 1042 | 1043 | async ts(msg) { 1044 | var d = new Date(); 1045 | var ts = `[${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}:${pad(d.getMilliseconds(), 100)}]`; 1046 | 1047 | console.log(`${ts} [${this.user.email}] ${msg}`) 1048 | 1049 | function pad(value, lt = 10) { 1050 | 1051 | var temp = value; 1052 | if (value < lt) { 1053 | temp = '0' + value; 1054 | } else { 1055 | temp = value; 1056 | } 1057 | 1058 | if (lt >= 100) { 1059 | if (value < 10) { 1060 | temp = '0' + temp; 1061 | } 1062 | } 1063 | 1064 | return temp; 1065 | } 1066 | } 1067 | 1068 | } 1069 | 1070 | 1071 | 1072 | function view(body) { 1073 | fs.writeFileSync('view.html', body) 1074 | } 1075 | 1076 | function wait(ms) { 1077 | return new Promise(resolve => setTimeout(resolve, ms)); 1078 | } 1079 | 1080 | function success_discord_webhook(order, product, email, time, thumbnail, size) { 1081 | 1082 | // Email privacy only first 2 and last two characters. 1083 | var em = email.split('@'); 1084 | var f3 = em[0].substr(0,2) 1085 | var l3 = em[0].substr(-2); 1086 | 1087 | var l = em[0].length - 4; 1088 | // console.log(l); 1089 | 1090 | email = `${f3}${"\\**".repeat((~~(em[0].length - 4) / 2))}${l3}@${em[1]}` 1091 | 1092 | 1093 | var embed = new Discord.RichEmbed() 1094 | .setTitle(`Successful Off White Checkout`) 1095 | .setDescription(`Successful checkout for ${product}`) 1096 | .setColor('#e74649') 1097 | .setTimestamp() 1098 | .setURL(`https://www.off---white.com/en/GB/orders/${order}`) 1099 | .setFooter('Developed by @unreleased and @notchefbob', 'https://notify.express/assets/img/express.png') 1100 | .setThumbnail(thumbnail) 1101 | .addField('Email', email, true) 1102 | .addField('Checkout Time', time, true) 1103 | .addField('Size', `${size}US`, true) 1104 | .addField('Order', order, true) 1105 | 1106 | webhook.send(embed) 1107 | } 1108 | 1109 | 1110 | 1111 | 1112 | async.each(tasks.users, function(user) { 1113 | new Instance(user); 1114 | }) --------------------------------------------------------------------------------