From e8b95176640ccaac67f4a468107320cc47a1d161 Mon Sep 17 00:00:00 2001 From: Matthias Guillitte Date: Tue, 18 Mar 2025 19:40:55 +0100 Subject: [PATCH] Added Hellcase Battle job Still need testing and making proper notifications --- app/Browser/Jobs/Hellcase/HellcaseJob.php | 10 +- .../HellcaseBattles/HellcaseBattlesJob.php | 145 ++++++++++++++++++ app/Models/HellcaseBattle.php | 17 ++ app/Models/Job.php | 10 ++ ..._03_16_164445_add_hellcase_battles_job.php | 82 ++++++++++ .../js/Components/Layout/Job/JobFormField.vue | 2 +- routes/api.php | 1 + routes/console.php | 2 + todo.md | 3 + 9 files changed, 266 insertions(+), 6 deletions(-) create mode 100644 app/Browser/Jobs/HellcaseBattles/HellcaseBattlesJob.php create mode 100644 app/Models/HellcaseBattle.php create mode 100644 database/migrations/2025_03_16_164445_add_hellcase_battles_job.php diff --git a/app/Browser/Jobs/Hellcase/HellcaseJob.php b/app/Browser/Jobs/Hellcase/HellcaseJob.php index 3772e9c..0a3d1d5 100644 --- a/app/Browser/Jobs/Hellcase/HellcaseJob.php +++ b/app/Browser/Jobs/Hellcase/HellcaseJob.php @@ -25,12 +25,12 @@ class HellcaseJob extends BrowserJob implements ShouldBeUniqueUntilProcessing private const STEAM_LOGIN_THRESHOLD = 5 * 60; // 5 minutes private const APPROXIMATIVE_RUNNING_MINUTES = 2; - private JobRun $jobRun; + protected JobRun $jobRun; - public function __construct() + public function __construct($jobId = 2) { Log::info("Constructing HellcaseJob"); - parent::__construct(2); + parent::__construct($jobId); } public function run(Browser $browser): ?JobRun @@ -89,7 +89,7 @@ class HellcaseJob extends BrowserJob implements ShouldBeUniqueUntilProcessing } } - private function signin(Browser $browser) + protected function signin(Browser $browser) { try { $browser->clickAtXPath('//button[.//span[text() = "Sign in"]]'); @@ -390,7 +390,7 @@ class HellcaseJob extends BrowserJob implements ShouldBeUniqueUntilProcessing $browser->clickAtXPath('//*[contains(text(), "Edit Profile")]'); } - private function removePopups(Browser $browser) + protected function removePopups(Browser $browser) { // $browser->script('document.querySelector("div.app-modal")[0].remove();'); // $browser->driver->executeScript('document.querySelector("div.app-modal")[0].remove();'); diff --git a/app/Browser/Jobs/HellcaseBattles/HellcaseBattlesJob.php b/app/Browser/Jobs/HellcaseBattles/HellcaseBattlesJob.php new file mode 100644 index 0000000..4d36e68 --- /dev/null +++ b/app/Browser/Jobs/HellcaseBattles/HellcaseBattlesJob.php @@ -0,0 +1,145 @@ +jobInfos = Job::find($this->jobId)->jobInfosTable(); + Log::info("Running HellcaseBattlesJob"); + $this->jobRun = new JobRun([ + "job_id" => $this->jobId, + "success" => false, + ]); + $this->jobRun->save(); + + $browser->visit('https://hellcase.com'); + $browser->waitForText("CASES", 30, true); + $this->removePopups($browser); + sleep(5); + $this->signin($browser); + + $this->saveInterestingBattles($browser); + + $this->sendFinishedBattles($browser); + + $this->createNewBattles(); + + $this->jobRun->success = true; + $this->jobRun->save(); + + Log::info("HellcaseBattlesJob run ended"); + + return $this->jobRun; + } + + /** + * Save current cases battles to database for later processing + * @param \Laravel\Dusk\Browser $browser + * @return void + */ + private function saveInterestingBattles(Browser $browser) + { + $battleIndex = 0; // Index of the battle to get info from + $running = true; + while ($running) { + $browser->visit('https://hellcase.com/casebattle'); + $browser->waitForText("CASES", 30, true); + + AllNotification::send(new JobDebugNotification($this->jobId, "I hate niggers")); + + // Sort by price + try { + $sortByPriceDiv = $browser->driver->findElement(WebDriverBy::xpath("//*[span[contains(text(), 'Value')]]")); + $sortByPriceDiv->click(); + } catch (Exception $e) { + AllNotification::send(new JobDebugNotification($this->jobId, "Failed to sort by price")); + return; + } + + sleep(5); + + $battles = $browser->driver->findElements(WebDriverBy::xpath("//*[contains(@class, 'casebattle-table__item')]")); + $battle = $battles[$battleIndex]; + $battleIndex++; + $browser->scrollIntoView(".casebattle-table__item:nth-child(" . max($battleIndex -1, 1) . ")"); + sleep(2); + $battleValue = floatval( + explode( + "\n", + $battle->findElement(WebDriverBy::xpath("./div/div[contains(@class, 'core-price')]"))->getDomProperty("innerText") + )[1] + ); + + if ($battleValue < floatval($this->jobInfos->get("hellcase_battles_minimum_value"))) { + $running = false; + break; + } + + $battleLinkButton = $battle->findElement(WebDriverBy::xpath('./div//button[text() = "watch"]')); + $battleLinkButton->sendKeys("\n"); + sleep(3); + $battleLink = $browser->driver->getCurrentURL(); + + $this->battlesToAdd[$battleLink] = $battleValue; + } + } + + private function sendFinishedBattles(Browser $browser) { + // foreach battle that we didn"t already planned to add with $this->battlesToAdd + foreach (HellcaseBattle::all() as $battle) { + dump($battle); + if (!array_key_exists($battle->getUrl(), $this->battlesToAdd)) { + dump("finished"); + $browser->visit($battle->getUrl()); + + try { + $browser->waitForText("Started at"); + // Send the battle + $this->sendBattle($browser, $battle); + } catch (Exception $e) { // Battle is not finished or error (like battle cancelled) + } + + $battle->delete(); + } + } + } + + private function sendBattle(Browser $browser, HellcaseBattle $battle) { + AllNotification::send(new JobDebugNotification($this->jobId, "Battle sent" . $battle->getUrl())); + } + + private function createNewBattles() { + foreach ($this->battlesToAdd as $battleLink => $battleValue) { + $battleLink = explode("/", $battleLink); + HellcaseBattle::firstOrCreate([ + "battle_id" => $battleLink[count($battleLink) - 1], + "value" => $battleValue, + ]); + } + } +} diff --git a/app/Models/HellcaseBattle.php b/app/Models/HellcaseBattle.php new file mode 100644 index 0000000..5d083bb --- /dev/null +++ b/app/Models/HellcaseBattle.php @@ -0,0 +1,17 @@ +battle_id}"; + } +} diff --git a/app/Models/Job.php b/app/Models/Job.php index bf0515b..11111b7 100644 --- a/app/Models/Job.php +++ b/app/Models/Job.php @@ -21,6 +21,16 @@ class Job extends Model return $this->hasMany(JobInfo::class)->with("jobInfoType")->orderBy("created_at"); } + /** + * Get an associative collection of the job infos with their values + * @return \Illuminate\Database\Eloquent\Collection> + */ + public function jobInfosTable() { + return $this->jobInfos->mapWithKeys(function ($jobInfo) { + return [$jobInfo->key => $jobInfo->value]; + }); + } + public function jobRuns() { return $this->hasMany(JobRun::class)->orderBy("created_at"); diff --git a/database/migrations/2025_03_16_164445_add_hellcase_battles_job.php b/database/migrations/2025_03_16_164445_add_hellcase_battles_job.php new file mode 100644 index 0000000..8dd6616 --- /dev/null +++ b/database/migrations/2025_03_16_164445_add_hellcase_battles_job.php @@ -0,0 +1,82 @@ + $newJobId, + "name" => "Hellcase Battles", + "description" => "Envoie les meilleures battles d'Hellcase", + ]); + + JobInfo::forceCreate([ + "key" => "hellcase_battles_discord_webhook_url", + "name" => "Webhook Discord", + "description" => "Le lien discord webhook utilisé pour envoyer les meilleures battles d'Hellcase.\nSi aucun n'est spécifié, le webhook Discord des paramètres généraux sera utilisé.", + "placeholder" => "https://discord.com/api/webhooks/...", + "is_required" => false, + "job_info_type_id" => 4, + "job_id" => $newJobId, + ]); + + JobInfoType::forceCreate([ + "id" => 5, + "name" => "number", + ]); + JobInfoType::forceCreate([ + "id" => 6, + "name" => "boolean", + ]); + + JobInfo::forceCreate([ + "key" => "hellcase_battles_minimum_value", + "name" => "Valeur minimum des battles", + "description" => "La valeur minimale qu'une battle doit avoir pour être envoyée, en euros.", + "placeholder" => "1000", + "job_info_type_id" => 5, + "job_id" => $newJobId, + ]); + + JobInfo::forceCreate([ + "key" => "hellcase_battles_allow_bots", + "name" => "Autoriser les battles avec bots", + "description" => "Envoyer les battles avec un seul joueur et des bots.", + "is_required" => false, + "job_info_type_id" => 6, + "job_id" => $newJobId, + ]); + + Schema::create('hellcase_battles', function (Blueprint $table) { + $table->id(); + + $table->string("battle_id")->unique(); + $table->float("value"); + + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Job::where("id", 3)->delete(); + JobInfo::where("job_id", 3)->delete(); + JobInfoType::whereIn("id", [5, 6])->delete(); + + Schema::dropIfExists('hellcase_battles'); + } +}; diff --git a/resources/js/Components/Layout/Job/JobFormField.vue b/resources/js/Components/Layout/Job/JobFormField.vue index 3db71d9..03b2982 100644 --- a/resources/js/Components/Layout/Job/JobFormField.vue +++ b/resources/js/Components/Layout/Job/JobFormField.vue @@ -18,7 +18,7 @@ const jobInfoType = props.jobInfo.job_info_type.name;
{{ jobInfo.description }} - +
diff --git a/routes/api.php b/routes/api.php index 29cbc01..ea27de1 100644 --- a/routes/api.php +++ b/routes/api.php @@ -12,6 +12,7 @@ Route::get('/jobs', function (Request $request) { Route::get('/test/{id}', function (Request $request, $id, BrowserJobsInstances $BrowserJobsInstances) { $log = $BrowserJobsInstances->getJobInstance($id)->execute(); + dump($log); return response()->json(['message' => 'Job ' . $id . ' ran', 'jobRun' => $log->load('artifacts')]); }); diff --git a/routes/console.php b/routes/console.php index 3d242da..f9caefa 100644 --- a/routes/console.php +++ b/routes/console.php @@ -1,6 +1,7 @@ monthly()->onOneServer()->withoutOverlapping // Jobs Schedule::job(new HellcaseJob)->daily()->onOneServer()->withoutOverlapping()->name('hellcase')->description('Hellcase job'); // Schedule::job(new HellcaseJob)->everyMinute()->onOneServer()->withoutOverlapping()->name('hellcase')->description('Hellcase job'); +Schedule::job(new HellcaseBattlesJob)->hourly()->onOneServer()->withoutOverlapping()->name('hellcase_battles')->description('Hellcase battles job'); diff --git a/todo.md b/todo.md index 9354762..9d6e7da 100644 --- a/todo.md +++ b/todo.md @@ -13,6 +13,9 @@ - Websocket installé - Serveur php plus propre (nginx, apache, n'importe) - Epic games + Pas l'air possible avec cloudflare +- Petit bug, quand l'on enregistre un formulaire avec une erreur, l'url a un argument GET ?error=mon%24erreur + Du coup dans la nav le job actuel n'est plus reconnu ## Pour deploy Lama