downloadFolder = base_path($this->downloadFolder); } public function run(Browser $browser): ?JobRun { $startTime = microtime(true); Log::info("Running EldoradoRobuxPriceSentryJob"); $this->jobInfos = Job::find($this->jobId)->jobInfosTable(); $this->jobRun = new JobRun([ "job_id" => $this->jobId, "success" => false, ]); $this->jobRun->save(); dump("visiting " . microtime(true) - $startTime); $browser->visit(self::LINK); sleep(5); $this->sendPrices($browser); $this->jobRun->success = true; $this->jobRun->save(); Log::info("EldoradoRobuxPriceSentryJob run ended"); return $this->jobRun; } /** * @inheritDoc */ public function runTest(Browser $browser): ?JobRun { $this->jobInfos = Job::find($this->jobId)->jobInfosTable(); try { $browser->visit(self::LINK); sleep(5); return $this->makeSimpleJobRun( true, "Test réussi", "Datboi a réussi à charger la page Eldorado." ); } catch (\Exception $e) { return $this->makeSimpleJobRun( false, "Test échoué", "Datboi n'a pas réussi à charger la page Eldorado :\n" . $e->getMessage() ); } } private function sendPrices(Browser $browser): void { $offers = $this->getOffers($browser); $priceThreshold = $this->textToFloat($this->jobInfos->get("eldorado_robux_price_threshold")); $stockThreshold = $this->jobInfos->get("eldorado_stock_quantity_threshold") == null ? null : $this->getIntFromText($this->jobInfos->get("eldorado_stock_quantity_threshold")); foreach ($offers as $offer) { Log::info("EldoradoRobuxPriceSentryJob: found offer - Price: " . $offer->robuxPrice . " €, Stock: " . $offer->robuxStock); // Checks if ($offer->robuxPrice > $priceThreshold || ($stockThreshold !== null && $offer->robuxStock < $stockThreshold)) { continue; // Not matching the criteria } // Logging and artifact Log::info("EldoradoRobuxPriceSentryJob: lowest price = $offer->robuxPrice €, threshold = $priceThreshold € - stock = $offer->robuxStock " . ($stockThreshold !== null ? "- stock threshold = $stockThreshold" : "")); $this->jobRun->addArtifact(new JobArtifact([ "name" => "Trouvé le prix le plus bas", "content" => "Prix le plus bas : $offer->robuxPrice €/Robux - Seuil défini : $priceThreshold €/Robux Stock disponible : $offer->robuxStock " . ($stockThreshold !== null ? "- Seuil de stock défini : $stockThreshold" : ""), ])); // get the link $offerLink = $this->getLinkFromOfferIndex($browser, $offer->listingIndex); // Send alert $this->sendAlertNotification( $offer->robuxPrice, $priceThreshold, $offer->robuxStock, $stockThreshold, $offerLink ); Log::info("EldoradoRobuxPriceSentryJob: alert sent"); return; // Stops at first matching offer } Log::info("EldoradoRobuxPriceSentryJob: no alert sent"); } private function sendAlertNotification(float $lowestPrice, float $priceThreshold, int $stock, int|null $stockThreshold, string $link): void { $options = []; if ($this->jobInfos->get("eldorado_robux_price_discord_webhook") !== null) { // Custom discord webhook $options["discord_webhook_url"] = $this->jobInfos->get("eldorado_robux_price_discord_webhook"); } AllNotification::send( new EldoradoRobuxPriceNotification( $this->jobId, $lowestPrice, $priceThreshold, $stock, $stockThreshold, $link ), $options ); $this->jobRun->addArtifact(new JobArtifact([ "name" => "Envoyé une alerte", "content" => "" ])); } /** * Retrieve the url for the given listing index. * WILL NOT REDIRECT BACK TO THE OFFERS PAGE. * @param Browser $browser * @param int $listingIndex * @return string */ private function getLinkFromOfferIndex(Browser $browser, int $listingIndex): string { if ($listingIndex == self::LOWEST_PRICE_INDEX) { return self::LINK; } $offerCardElements = $this->getOffersCardElements($browser); $offerCardElement = $offerCardElements[$listingIndex]; // Click the card element $offerCardElement->click(); sleep(2); // return the current url return $browser->driver->getCurrentURL(); } /** * Get the list of offers on the page * @return EldoradoRobuxOffer[] */ private function getOffers(Browser $browser): array { $offers = []; // First offer (lowest price) (special case) $firstOffer = $this->getFirstOffer($browser); $offers[] = $firstOffer; // Other offers $offerCardElements = $this->getOffersCardElements($browser); foreach ($offerCardElements as $index => $offerCardElement) { $priceElement = $offerCardElement->findElement(WebDriverBy::xpath('.//eld-offer-price/strong')); $priceText = $priceElement->getText(); // Ex: " 0,00520 € " $price = $this->textToFloat($priceText); // span.value next to a span.label with the text containing "stock" $stockElement = $offerCardElement->findElement(WebDriverBy::xpath('.//span[@class="label" and contains(text(), "stock")]/following-sibling::*[@class="value"]')); $stockText = $stockElement->getText(); // Ex: "En stock : 5000 unités" $stock = $this->getIntFromText($stockText); $offers[] = new EldoradoRobuxOffer( robuxStock: $stock, robuxPrice: $price, listingIndex: $index ); } return $offers; } private function getFirstOffer(Browser $browser): EldoradoRobuxOffer { $lowestPriceElement = $browser->driver->findElement(WebDriverBy::xpath('(//eld-offer-price)[2]/strong')); $lowestPriceText = $lowestPriceElement->getText(); // Ex: " 0,00478 € " $lowestPrice = $this->textToFloat($lowestPriceText); //$lowestPrice = $lowestPrice / 1000; // Price per Robux // TODO : Look at the entire text to try to understand if it is per 1k or per single Robux $lowestStockElement = $browser->driver->findElement(WebDriverBy::xpath('(//eld-quantity-details-wrapper)[1]//div[@class="quantity"]')); $lowestStockText = $lowestStockElement->getText(); // Ex: "En stock : 9942199 unité" $lowestStock = $this->getIntFromText($lowestStockText); return new EldoradoRobuxOffer($lowestStock, $lowestPrice, self::LOWEST_PRICE_INDEX); } private function getOffersCardElements(Browser $browser): array { return $browser->driver->findElements(WebDriverBy::xpath('//div[contains(@class, "offer-seller-card")]')); } private function textToFloat(string $text): float { return floatval(str_replace(["€", ","], ["", "."], trim($text))); } private function getIntFromText(string $text): int { $text = str_replace([" ", " "], "", $text); // Remove spaces preg_match('/\d+/', $text, $matches); return intval($matches[0]); } }