Sort of working beta
This commit is contained in:
272
app/Browser/BrowserJob.php
Normal file
272
app/Browser/BrowserJob.php
Normal file
@ -0,0 +1,272 @@
|
||||
<?php
|
||||
|
||||
namespace App\Browser;
|
||||
|
||||
use App\Browser\JobArtifacts\JobRunArtifact;
|
||||
use App\Exception\JobException;
|
||||
use App\Models\JobArtifact;
|
||||
use App\Models\JobRun;
|
||||
use Facebook\WebDriver\Chrome\ChromeOptions;
|
||||
use Facebook\WebDriver\Remote\DesiredCapabilities;
|
||||
use Facebook\WebDriver\Remote\RemoteWebDriver;
|
||||
use Facebook\WebDriver\Remote\RemoteWebElement;
|
||||
use Facebook\WebDriver\WebDriverBy;
|
||||
use Illuminate\Support\Collection;
|
||||
use Laravel\Dusk\Browser;
|
||||
use Laravel\Dusk\TestCase as BaseTestCase;
|
||||
use Closure;
|
||||
use PHPUnit\Framework\Attributes\BeforeClass;
|
||||
use Exception;
|
||||
use Laravel\Dusk\Chrome\SupportsChrome;
|
||||
use Laravel\Dusk\Concerns\ProvidesBrowser;
|
||||
use Laravel\Dusk;
|
||||
use Throwable;
|
||||
|
||||
abstract class BrowserJob
|
||||
{
|
||||
use SupportsChrome, ProvidesBrowser;
|
||||
|
||||
public int $jobId;
|
||||
|
||||
public function __construct(int $jobId)
|
||||
{
|
||||
$this->jobId = $jobId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the callback in a browser
|
||||
* @param Closure $callback function with a Browser as parameter
|
||||
* @return void
|
||||
*/
|
||||
private function executeInBrowser(Closure $callback): ?JobRun
|
||||
{
|
||||
|
||||
$this->prepare();
|
||||
$this->setUp();
|
||||
|
||||
$this->browse(function (Browser $browser) use ($callback, &$log) {
|
||||
try {
|
||||
$log = $callback($browser);
|
||||
// } catch (Exception $e) {
|
||||
// $browser->screenshot("failure-{$this->jobId}");
|
||||
// dump($e);
|
||||
// throw $e;
|
||||
}
|
||||
catch (Throwable $e) {
|
||||
$browser->screenshot("failure-{$this->jobId}");
|
||||
dump($e);
|
||||
throw $e;
|
||||
} finally {
|
||||
$browser->quit();
|
||||
return [];
|
||||
}
|
||||
});
|
||||
|
||||
return $log;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job
|
||||
* @return void
|
||||
*/
|
||||
public function execute(): ?JobRun {
|
||||
return $this->executeInBrowser(function (Browser $browser): ?JobRun {
|
||||
return $this->run($browser);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job test
|
||||
* @return void
|
||||
*/
|
||||
public function executeTest(): ?JobRun {
|
||||
return $this->executeInBrowser(function (Browser $browser): ?JobRun {
|
||||
return $this->runTest($browser);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Steps that run in the browser
|
||||
* @param \Laravel\Dusk\Browser $browser
|
||||
* @return void
|
||||
*/
|
||||
abstract public function run(Browser $browser): ?JobRun;
|
||||
|
||||
abstract public function runTest(Browser $browser): ?JobRun;
|
||||
|
||||
/**
|
||||
* Prepare for Dusk test execution.
|
||||
* @unused
|
||||
*/
|
||||
#[BeforeClass]
|
||||
public static function prepare(): void
|
||||
{
|
||||
if (config("dusk.driver.url") == null && !(isset($_ENV['LARAVEL_SAIL']) && $_ENV['LARAVEL_SAIL'] == '1')) {
|
||||
static::startChromeDriver(['--port=9515']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the base URL with Dusk.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function setUp(): void
|
||||
{
|
||||
Browser::$baseUrl = "https://pdftools.matthiasg.dev/";
|
||||
|
||||
Browser::$storeScreenshotsAt = base_path('app/Browser/screenshots');
|
||||
|
||||
Browser::$storeConsoleLogAt = base_path('app/Browser/console');
|
||||
|
||||
Browser::$storeSourceAt = base_path('app/Browser/source');
|
||||
|
||||
/*Browser::$userResolver = function () {
|
||||
return $this->user();
|
||||
}; */
|
||||
}
|
||||
|
||||
protected function makeSimpleJobRun(bool $success, string $name, string $content): JobRun {
|
||||
$artifact = new JobRun([
|
||||
"job_id" => $this->jobId,
|
||||
"success" => $success
|
||||
]);
|
||||
$artifact->save();
|
||||
$artifact->artifacts()->save(new JobArtifact([
|
||||
"name" => $name,
|
||||
"content" => $content,
|
||||
]));
|
||||
return $artifact;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the RemoteWebDriver instance.
|
||||
*/
|
||||
protected function driver(): RemoteWebDriver
|
||||
{
|
||||
$options = (new ChromeOptions)->addArguments(collect([
|
||||
$this->shouldStartMaximized() ? '--start-maximized' : '--window-size=1360,1020',
|
||||
'--disable-search-engine-choice-screen',
|
||||
'--disable-gpu',
|
||||
'--no-sandbox',
|
||||
'--disable-setuid-sandbox',
|
||||
'--whitelisted-ips=""',
|
||||
'--disable-dev-shm-usage',
|
||||
'--user-data-dir=/home/seluser/profile/',
|
||||
])->all());
|
||||
|
||||
return RemoteWebDriver::create(
|
||||
config("dusk.driver.url", 'http://localhost:9515'),
|
||||
DesiredCapabilities::chrome()->setCapability(
|
||||
ChromeOptions::CAPABILITY,
|
||||
$options
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function terminate() {
|
||||
$this->browse(function (Browser $browser) {
|
||||
$browser->quit();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the Dusk command has disabled headless mode.
|
||||
*/
|
||||
protected function hasHeadlessDisabled(): bool
|
||||
{
|
||||
return isset($_SERVER['DUSK_HEADLESS_DISABLED']) ||
|
||||
isset($_ENV['DUSK_HEADLESS_DISABLED']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the browser window should start maximized.
|
||||
*/
|
||||
protected function shouldStartMaximized(): bool
|
||||
{
|
||||
return isset($_SERVER['DUSK_START_MAXIMIZED']) ||
|
||||
isset($_ENV['DUSK_START_MAXIMIZED']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an "after class" tear down callback.
|
||||
*
|
||||
* @param \Closure $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function afterClass(Closure $callback)
|
||||
{
|
||||
static::$afterClassCallbacks[] = $callback;
|
||||
}
|
||||
|
||||
public static function name()
|
||||
{
|
||||
return "test";
|
||||
}
|
||||
|
||||
public static function dataName()
|
||||
{
|
||||
return "dataTest";
|
||||
}
|
||||
|
||||
|
||||
// BROWSER MACROS
|
||||
protected function waitForAndClickText(Browser $browser, string $text, int $timeout = 30, bool $ignoreCase = true) {
|
||||
$browser->waitForText($text, $timeout, $ignoreCase);
|
||||
$this->findElementContainingText($browser, $text, $ignoreCase)?->click();
|
||||
}
|
||||
|
||||
protected function waitForElementContainingTextAndGetIt(Browser $browser, string $text, int $timeout = 30, bool $ignoreCase = true): RemoteWebElement|null {
|
||||
try {
|
||||
$browser->waitForText($text, $timeout, $ignoreCase);
|
||||
return $this->findElementContainingText($browser, $text, $ignoreCase);
|
||||
} catch (Exception $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected function findElementContainingText(Browser $browser, string $text, bool $ignoreCase = true): RemoteWebElement|null {
|
||||
try {
|
||||
if ($ignoreCase) {
|
||||
return $browser->driver->findElement(WebDriverBy::xpath("//*[{$this->xpathContainsIgnoreCase($text)}]"));
|
||||
} else {
|
||||
return $browser->driver->findElement(WebDriverBy::xpath("//*[contains(text(), \"{$text}\")]"));
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private function xpathContainsIgnoreCase(string $needle, string $haystack = "text()") {
|
||||
$needle = strtolower($needle);
|
||||
return "contains(translate({$haystack}, \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\", \"abcdefghijklmnopqrstuvwxyz\"), \"{$needle}\")";
|
||||
}
|
||||
|
||||
protected function waitForAndClickElementContainingText(Browser $browser, string $elementXpath, string $text, int $timeout = 30, bool $ignoreCase = true) {
|
||||
$browser->waitForText($text, $timeout, $ignoreCase);
|
||||
$this->findElementContainingElementWithText($browser, $elementXpath, $text, $ignoreCase)?->click();
|
||||
}
|
||||
|
||||
protected function waitForElementContainingElementWithTextAndGetIt(Browser $browser, string $elementXpath, string $text, int $timeout = 30, bool $ignoreCase = true): RemoteWebElement|null {
|
||||
try {
|
||||
$browser->waitForText($text, $timeout, $ignoreCase);
|
||||
sleep(2);
|
||||
return $this->findElementContainingElementWithText($browser, $elementXpath, $text, $ignoreCase);
|
||||
} catch (Exception $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected function findElementContainingElementWithText(Browser $browser, string $elementXpath, string $text, bool $ignoreCase = true): RemoteWebElement|null {
|
||||
try {
|
||||
if ($ignoreCase) {
|
||||
dump("{$elementXpath}[.//*[{$this->xpathContainsIgnoreCase($text)}]]");
|
||||
return $browser->driver->findElement(WebDriverBy::xpath("{$elementXpath}[.//*[{$this->xpathContainsIgnoreCase($text)}]]"));
|
||||
} else {
|
||||
return $browser->driver->findElement(WebDriverBy::xpath("{$elementXpath}[.//*[contains(text(), \"{$text}\")]]"));
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
43
app/Browser/Components/Hellcase/MainNav.php
Normal file
43
app/Browser/Components/Hellcase/MainNav.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Browser\Components\Hellcase;
|
||||
|
||||
use Laravel\Dusk\Browser;
|
||||
use Laravel\Dusk\Component as BaseComponent;
|
||||
|
||||
class MainNav extends BaseComponent
|
||||
{
|
||||
/**
|
||||
* Get the root selector for the component.
|
||||
*/
|
||||
public function selector(): string
|
||||
{
|
||||
return 'header.header';
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the browser page contains the component.
|
||||
*/
|
||||
public function assert(Browser $browser): void
|
||||
{
|
||||
$browser->assertVisible($this->selector());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the element shortcuts for the component.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function elements(): array
|
||||
{
|
||||
return [
|
||||
'@logo' => 'a.header-logo',
|
||||
'@daily-free-link' => 'a[href="/dailyfree"]',
|
||||
];
|
||||
}
|
||||
|
||||
public function goToHome(Browser $browser) {
|
||||
$browser->scrollIntoView('@logo');
|
||||
$browser->click('@logo');
|
||||
}
|
||||
}
|
29
app/Browser/Jobs/Hellcase/HellcaseDailyFreeScreenshot.php
Normal file
29
app/Browser/Jobs/Hellcase/HellcaseDailyFreeScreenshot.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Browser\Jobs\Hellcase;
|
||||
|
||||
use Laravel\Dusk\Browser;
|
||||
use function rtrim;
|
||||
|
||||
|
||||
class HellcaseDailyFreeScreenshot {
|
||||
public const IMG_FILE_NAME = "Hellcase-dailyFreeLoot";
|
||||
|
||||
/**
|
||||
* QR code validity in seconds
|
||||
* @var int
|
||||
*/
|
||||
public const QR_CODE_VALIDITY = 20;
|
||||
|
||||
public static function getImgFileAbsolutePath(): string {
|
||||
return rtrim(Browser::$storeScreenshotsAt, '/') . "/Hellcase/" . static::IMG_FILE_NAME;
|
||||
}
|
||||
|
||||
public static function getImgFileProjectPath(): string {
|
||||
return app_path("Browser/screenshots/Hellcase/" . static::IMG_FILE_NAME);
|
||||
}
|
||||
|
||||
public static function getImgFileExternalPath(): string {
|
||||
return "screenshots/Hellcase/" . static::IMG_FILE_NAME;
|
||||
}
|
||||
}
|
315
app/Browser/Jobs/Hellcase/HellcaseJob.php
Normal file
315
app/Browser/Jobs/Hellcase/HellcaseJob.php
Normal file
@ -0,0 +1,315 @@
|
||||
<?php
|
||||
|
||||
namespace App\Browser\Jobs\Hellcase;
|
||||
|
||||
use App\Browser\BrowserJob;
|
||||
use App\Browser\Components\Hellcase\MainNav;
|
||||
use App\Models\JobArtifact;
|
||||
use App\Browser\JobArtifacts\JobRunArtifact;
|
||||
use App\Browser\Jobs\Hellcase\HellcaseLoginQrCode;
|
||||
use App\Models\JobRun;
|
||||
use App\Notification\NotificationBody\Hellcase\HellcaseNotificationDailyFreeBody;
|
||||
use App\Notification\NotificationBody\Hellcase\HellcaseNotificationLoginBody;
|
||||
use App\Notification\Notifications\Hellcase\HellcaseNotificationDailyFree;
|
||||
use App\Notification\Notifications\Hellcase\HellcaseNotificationLogin;
|
||||
use App\Notification\Providers\AllNotification;
|
||||
use Dom\XPath;
|
||||
use Facebook\WebDriver\WebDriverBy;
|
||||
use Laravel\Dusk\Browser;
|
||||
|
||||
class HellcaseJob extends BrowserJob
|
||||
{
|
||||
private const STEAM_LOGIN_THRESHOLD = 5 * 60; // 5 minutes
|
||||
|
||||
private JobRun $jobRun;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct(2);
|
||||
}
|
||||
|
||||
public function run(Browser $browser): ?JobRun
|
||||
{
|
||||
$this->jobRun = new JobRun([
|
||||
"job_id" => $this->jobId,
|
||||
"success" => false,
|
||||
]);
|
||||
$this->jobRun->save();
|
||||
|
||||
$browser->visit('https://hellcase.com');
|
||||
sleep(5);
|
||||
$this->removePopups($browser);
|
||||
sleep(5);
|
||||
$this->signin($browser);
|
||||
$this->joinFreeGiveaways($browser);
|
||||
$this->getDailyFree($browser);
|
||||
|
||||
$this->jobRun->success = true;
|
||||
$this->jobRun->save();
|
||||
|
||||
return $this->jobRun;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function runTest(Browser $browser): ?JobRun
|
||||
{
|
||||
try {
|
||||
$browser->visit('https://hellcase.com');
|
||||
sleep(5);
|
||||
$this->removePopups($browser);
|
||||
$this->signin($browser);
|
||||
return $this->makeSimpleJobRun(
|
||||
true,
|
||||
"Connexion réussie",
|
||||
"Datboi a réussi à se connecter sur Hellcase"
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
return $this->makeSimpleJobRun(
|
||||
true,
|
||||
"Connexion échouée",
|
||||
"Datboi n'a pas réussi à se connecter sur Hellcase :\n" . $e->getMessage()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function signin(Browser $browser)
|
||||
{
|
||||
try {
|
||||
$browser->clickAtXPath('//button[.//span[text() = "Sign in"]]');
|
||||
} catch (\Exception $e) {
|
||||
return;
|
||||
}
|
||||
|
||||
sleep(3);
|
||||
$browser->clickAtXPath('//button[.//span[contains(text(), "Sign in through Steam")]]');
|
||||
sleep(5);
|
||||
|
||||
// QR CODE SCANNING
|
||||
try {
|
||||
$browser->waitForTextIn("div", "Or sign in with QR", 30, true);
|
||||
$qrCode = $browser->driver->findElement(WebDriverBy::xpath('//div[./*[contains(text(), "Or sign in with QR")]]'));
|
||||
|
||||
// Wait to be redirected to the Steam login page, while waiting take a new screenshot every 30 seconds
|
||||
$isBackOnHellcase = false;
|
||||
$secondsCounter = 0;
|
||||
while (!$isBackOnHellcase && $secondsCounter < self::STEAM_LOGIN_THRESHOLD) {
|
||||
// Take a screenshot of the QR code and send it
|
||||
$qrCode->takeElementScreenshot(HellcaseLoginQrCode::getImgFileAbsolutePath());
|
||||
AllNotification::send(
|
||||
new HellcaseNotificationLogin(
|
||||
$this->jobId,
|
||||
new HellcaseNotificationLoginBody()
|
||||
)
|
||||
);
|
||||
|
||||
try {
|
||||
$browser->waitForLocation("https://hellcase.com", HellcaseLoginQrCode::QR_CODE_VALIDITY); // The QR code is only valid for 20 seconds
|
||||
} catch (\Exception $e) {
|
||||
$secondsCounter += HellcaseLoginQrCode::QR_CODE_VALIDITY; // we've waited for QR_CODE_VALIDITY seconds
|
||||
continue;
|
||||
}
|
||||
$isBackOnHellcase = true;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// If the QR code is not found, we are not on the QR code page
|
||||
$isBackOnHellcase = true;
|
||||
} catch (\Throwable $e) {
|
||||
// If the QR code is not found, we are not on the QR code page
|
||||
$isBackOnHellcase = true;
|
||||
}
|
||||
|
||||
if ($isBackOnHellcase) {
|
||||
// Click a button tjat says "sign in"
|
||||
$browser->waitForText("By signing into steam.loginhell.com through Steam", 30, true);
|
||||
$browser->clickAtXPath('//input[@id = "imageLogin"]');
|
||||
sleep(30);
|
||||
}
|
||||
}
|
||||
|
||||
private function joinFreeGiveaways(Browser $browser)
|
||||
{
|
||||
try {
|
||||
$buttons = $browser->driver->findElements(WebDriverBy::xpath('//a[text() = "Join for free"]'));
|
||||
} catch (\Exception $e) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sizeof($buttons) == 0) {
|
||||
$this->jobRun->addArtifact(new JobArtifact([
|
||||
"name" => "Pas de concours joignable",
|
||||
"content" => ""
|
||||
]));
|
||||
}
|
||||
foreach ($buttons as $button) {
|
||||
$button->click();
|
||||
sleep(5);
|
||||
$this->joinGiveaway($browser);
|
||||
$browser->within(new MainNav, function (Browser $browser) {
|
||||
$browser->goToHome();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private function joinGiveaway(Browser $browser)
|
||||
{
|
||||
$joinButton = $browser->driver->findElement(WebDriverBy::xpath('//button[span[contains(text(), "Join for free")]]'));
|
||||
$joinButton->click();
|
||||
}
|
||||
|
||||
private function getDailyFree(Browser $browser)
|
||||
{
|
||||
$browser->visit('https://hellcase.com/dailyfree');
|
||||
$browser->waitForText("Get Daily free loot", 30, true);
|
||||
|
||||
// Do we fill the conditions ?
|
||||
if (sizeof(value: $browser->driver->findElements(WebDriverBy::xpath('//p[contains(text(), "Fulfill the conditions below")]'))) > 0) {
|
||||
$this->fillDailyFreeConditions($browser);
|
||||
}
|
||||
|
||||
// If we see "availible in 20 HR 49 MIN", parse the hours and minute and reschedule
|
||||
$availibleInButton = $this->waitForElementContainingTextAndGetIt($browser, "available", 30);
|
||||
if ($availibleInButton != null) {
|
||||
$hours = $availibleInButton->getText();
|
||||
$hours = explode(" ", $hours);
|
||||
$minutes = $hours[2];
|
||||
$hours = $hours[0];
|
||||
// $this->reschedule($hours);
|
||||
|
||||
$this->jobRun->addArtifact(new JobArtifact([
|
||||
"name" => "Cadeau gratuit pas encore disponible",
|
||||
"content" => "Le cadeau gratuit journalier sera disponible dans $hours heures et $minutes minutes.\nDatboi se fera un plaisir d'aller le chercher pour vous."
|
||||
]));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->waitForAndClickText($browser, "Get Free Bonus", 30, true);
|
||||
|
||||
$lootElement = $browser->driver->findElement(WebDriverBy::xpath('//div[contains(@class, "daily-free-win-bonus")]'));
|
||||
$lootElement->takeElementScreenshot(HellcaseDailyFreeScreenshot::getImgFileAbsolutePath());
|
||||
|
||||
AllNotification::send(
|
||||
new HellcaseNotificationDailyFree($this->jobId, new HellcaseNotificationDailyFreeBody())
|
||||
);
|
||||
|
||||
sleep(5000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Must be on the dailyfree page
|
||||
* @param \Laravel\Dusk\Browser $browser
|
||||
* @throws \Exception
|
||||
* @return void
|
||||
*/
|
||||
private function fillDailyFreeConditions(Browser $browser) {
|
||||
// 1. See what conditions we need to fullfill
|
||||
$conditions = [];
|
||||
$conditionsDivs = $browser->driver->findElements(WebDriverBy::xpath('//*[@class = "daily-free-requirement__heading-left"]'));
|
||||
for($i = 0; $i < sizeof($conditionsDivs); $i++) {
|
||||
$conditionDiv = $conditionsDivs[$i];
|
||||
// See if the element has the completed class
|
||||
$conditions[$i] = [
|
||||
"isFilled" => str_contains($conditionDiv->getAttribute("class"), "completed"),
|
||||
"text" => $conditionDiv->getText()
|
||||
];
|
||||
}
|
||||
|
||||
if (sizeof($conditions) == 0) {
|
||||
throw new \Exception("No dailyfree conditions found");
|
||||
}
|
||||
|
||||
if (!$conditions[0]["isFilled"]) {
|
||||
$this->changeSteamProfilePicture($browser);
|
||||
}
|
||||
if (!$conditions[1]["isFilled"]) {
|
||||
$this->changeSteamProfileToPublic($browser);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Must be on the dailyfree page
|
||||
* @param \Laravel\Dusk\Browser $browser
|
||||
* @return void
|
||||
*/
|
||||
private function changeSteamProfilePicture(Browser $browser) {
|
||||
// Get all of the availible image link
|
||||
$images = $browser->driver->findElements(WebDriverBy::xpath('//a[@class = "daily-free-user-requirement-avatar-item"]'));
|
||||
// Download the image from the second link in a special folder
|
||||
$imageLink = $images[1]->getAttribute("href");
|
||||
// Download the image in app/Browser/downloads/
|
||||
$imagePath = base_path("app/Browser/downloads/Hellcase/pp.jpg");
|
||||
file_put_contents($imagePath, file_get_contents($imageLink));
|
||||
|
||||
$this->goToSteamProfileSettings($browser);
|
||||
|
||||
// Wait for and click "Avatar"
|
||||
$this->waitForAndClickText($browser, "Avatar");
|
||||
|
||||
// Wait for and click "Upload your avatar"
|
||||
$browser->waitForText("Upload your avatar", 30, true);
|
||||
// $browser->clickAtXPath('//*[contains(text(), "Upload your avatar")]');
|
||||
|
||||
// Upload the downloaded image
|
||||
$browser->attach('input[type="file"]', $imagePath);
|
||||
|
||||
// Wait for and click save
|
||||
$this->waitForAndClickText($browser, "Save");
|
||||
|
||||
// Go back to dailyfree
|
||||
$browser->visit('https://hellcase.com/dailyfree');
|
||||
|
||||
sleep(10);
|
||||
|
||||
try {
|
||||
// wait and click "Check the condition"
|
||||
$this->waitForAndClickText($browser, "Check the condition");
|
||||
$browser->waitForText("Your Steam profile avatar does not match any of the ones specified in the", 30, true);
|
||||
} catch (\Exception $e) {
|
||||
// If the text is not found, the condition is filled
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private function changeSteamProfileToPublic(Browser $browser) {
|
||||
$this->goToSteamProfileSettings($browser);
|
||||
|
||||
// Wait for and click "Privacy Settings"
|
||||
$this->waitForAndClickText($browser, "Privacy Settings");
|
||||
|
||||
$dropdownButton = $browser->driver->findElement(WebDriverBy::xpath('//div[text() = "My profile"]/div'));
|
||||
$dropdownButton->click();
|
||||
|
||||
sleep(2);
|
||||
|
||||
// Div that contains the visible class ath the body root an element with public text
|
||||
$publicOption = $browser->driver->findElement(WebDriverBy::xpath('/body/div[contains(@class, "visible")]/div[contains(text(), "Public")]'));
|
||||
$publicOption->click();
|
||||
|
||||
// Go back to dailyfree
|
||||
$browser->visit('https://hellcase.com/dailyfree');
|
||||
}
|
||||
|
||||
/**
|
||||
* From the dailyfree page
|
||||
* @param \Laravel\Dusk\Browser $browser
|
||||
* @return void
|
||||
*/
|
||||
private function goToSteamProfileSettings(Browser $browser) {
|
||||
// Get the link that has text "Steam profile"
|
||||
$steamProfileLink = $browser->driver->findElement(WebDriverBy::xpath('//a[contains(text(), "Steam profile")]'));
|
||||
$browser->visit($steamProfileLink->getAttribute("href"));
|
||||
$browser->waitForText("Level");
|
||||
|
||||
// Click "Edit Profile
|
||||
$browser->clickAtXPath('//*[contains(text(), "Edit Profile")]');
|
||||
}
|
||||
|
||||
private function removePopups(Browser $browser)
|
||||
{
|
||||
// $browser->script('document.querySelector("div.app-modal")[0].remove();');
|
||||
// $browser->driver->executeScript('document.querySelector("div.app-modal")[0].remove();');
|
||||
}
|
||||
}
|
29
app/Browser/Jobs/Hellcase/HellcaseLoginQrCode.php
Normal file
29
app/Browser/Jobs/Hellcase/HellcaseLoginQrCode.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Browser\Jobs\Hellcase;
|
||||
|
||||
use Laravel\Dusk\Browser;
|
||||
use function rtrim;
|
||||
|
||||
|
||||
class HellcaseLoginQrCode {
|
||||
public const IMG_FILE_NAME = "SteamQRCode.png";
|
||||
|
||||
/**
|
||||
* QR code validity in seconds
|
||||
* @var int
|
||||
*/
|
||||
public const QR_CODE_VALIDITY = 20;
|
||||
|
||||
public static function getImgFileAbsolutePath(): string {
|
||||
return rtrim(Browser::$storeScreenshotsAt, '/') . "/Hellcase/" . static::IMG_FILE_NAME;
|
||||
}
|
||||
|
||||
public static function getImgFileProjectPath(): string {
|
||||
return app_path("Browser/screenshots/Hellcase/" . static::IMG_FILE_NAME);
|
||||
}
|
||||
|
||||
public static function getImgFileExternalPath(): string {
|
||||
return "screenshots/Hellcase/" . static::IMG_FILE_NAME;
|
||||
}
|
||||
}
|
41
app/Browser/Jobs/Parameters/ParametersJob.php
Normal file
41
app/Browser/Jobs/Parameters/ParametersJob.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Browser\Jobs\Parameters;
|
||||
|
||||
use App\Browser\BrowserJob;
|
||||
use App\Models\JobArtifact;
|
||||
use App\Models\JobRun;
|
||||
use App\Notification\Notifications\SimpleNotification;
|
||||
use App\Notification\Providers\AllNotification;
|
||||
use Laravel\Dusk\Browser;
|
||||
|
||||
class ParametersJob extends BrowserJob
|
||||
{
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function run(Browser $browser): ?JobRun
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function runTest(Browser $browser): ?JobRun
|
||||
{
|
||||
try {
|
||||
AllNotification::send(new SimpleNotification($this->jobId, "Test", "Test des notifications"));
|
||||
AllNotification::send(new SimpleNotification($this->jobId, "Test", "Test des notifications d'erreur", true));
|
||||
return $this->makeSimpleJobRun(true, "Envoi de notification réussi", "Datboi a réussi à envoyer des notifications");
|
||||
} catch (\Throwable $e) {
|
||||
return $this->makeSimpleJobRun(false, "Envoi de notification échoué", "Datboi n'a pas réussi à envoyer des notifications :\n" . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
2
app/Browser/downloads/.gitignore
vendored
Normal file
2
app/Browser/downloads/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
69
app/Http/Controllers/JobController.php
Normal file
69
app/Http/Controllers/JobController.php
Normal file
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Exception\JobException;
|
||||
use App\Models\Job;
|
||||
use App\Services\BrowserJobsInstances;
|
||||
use Cache;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use App\Exception\JobTestException;
|
||||
|
||||
class JobController extends Controller
|
||||
{
|
||||
|
||||
public function __construct(
|
||||
protected BrowserJobsInstances $jobInstances,
|
||||
) {
|
||||
}
|
||||
|
||||
public function show($jobId, Request $request)
|
||||
{
|
||||
return Inertia::render('Job', [
|
||||
'job' => Job::where('id', $jobId)->with('jobInfos')->first(),
|
||||
'error' => $request->input('error'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function update($jobId, Request $request)
|
||||
{
|
||||
$job = Job::where('id', $jobId)->first();
|
||||
$job->is_active = false; // Disable the job
|
||||
|
||||
$errors = [];
|
||||
|
||||
foreach ($job->jobInfos()->get() as $jobInfo) {
|
||||
$value = $request->input($jobInfo->id);
|
||||
if (!isset($value) && $jobInfo->is_required) {
|
||||
$errors[] = 'Le champ ' . $jobInfo->name . ' est requis.';
|
||||
continue;
|
||||
}
|
||||
|
||||
Cache::forget($jobInfo->key);
|
||||
|
||||
$jobInfo->value = $value;
|
||||
$jobInfo->save();
|
||||
}
|
||||
|
||||
$job->save();
|
||||
|
||||
if (count($errors) > 0) {
|
||||
return redirect()->route('jobs.show', ['job' => $job, 'error' => implode('<br />', $errors)]);
|
||||
}
|
||||
|
||||
$job->is_active = $request->input('is_active');
|
||||
$job->save();
|
||||
|
||||
return redirect()->route('jobs.show', ['job' => $job]);
|
||||
}
|
||||
|
||||
public function test($jobId, Request $request)
|
||||
{
|
||||
$log = $this->jobInstances->getJobInstance($jobId)->executeTest();
|
||||
if (!empty($log)) {
|
||||
return response()->json(['artifact' => $log->load('artifacts')]);
|
||||
}
|
||||
return response()->json([]);
|
||||
}
|
||||
}
|
@ -7,6 +7,22 @@ use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Job extends Model
|
||||
{
|
||||
/** @use HasFactory<\Database\Factories\JobFactory> */
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
"is_active",
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
"is_active" => "boolean",
|
||||
];
|
||||
|
||||
public function jobInfos()
|
||||
{
|
||||
return $this->hasMany(JobInfo::class)->with("jobInfoType")->orderBy("created_at");
|
||||
}
|
||||
|
||||
public function jobRuns()
|
||||
{
|
||||
return $this->hasMany(JobRun::class)->orderBy("created_at");
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,17 @@ namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class JobArtifact extends Model
|
||||
{
|
||||
/** @use HasFactory<\Database\Factories\JobArtifactFactory> */
|
||||
use HasFactory;
|
||||
protected $fillable = [
|
||||
"name",
|
||||
"content",
|
||||
];
|
||||
|
||||
public function jobRun(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(JobRun::class);
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,19 @@ use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class JobInfo extends Model
|
||||
{
|
||||
/** @use HasFactory<\Database\Factories\JobInfoFactory> */
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
"value",
|
||||
];
|
||||
|
||||
public function job()
|
||||
{
|
||||
return $this->belongsTo(Job::class);
|
||||
}
|
||||
|
||||
public function jobInfoType()
|
||||
{
|
||||
return $this->belongsTo(JobInfoType::class)->select("id", "name");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -7,6 +7,12 @@ use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class JobInfoType extends Model
|
||||
{
|
||||
/** @use HasFactory<\Database\Factories\JobInfoTypeFactory> */
|
||||
use HasFactory;
|
||||
protected $fillable = [
|
||||
"name",
|
||||
];
|
||||
|
||||
public function jobInfos()
|
||||
{
|
||||
return $this->hasMany(JobInfo::class);
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,34 @@ namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class JobRun extends Model
|
||||
{
|
||||
/** @use HasFactory<\Database\Factories\JobRunFactory> */
|
||||
use HasFactory;
|
||||
protected $fillable = [
|
||||
"job_id",
|
||||
"success",
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
"success" => "boolean",
|
||||
];
|
||||
|
||||
protected $with = ['artifacts'];
|
||||
|
||||
public function job(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Job::class);
|
||||
}
|
||||
|
||||
public function artifacts(): HasMany
|
||||
{
|
||||
return $this->hasMany(JobArtifact::class);
|
||||
}
|
||||
|
||||
public function addArtifact(JobArtifact $artifact): void
|
||||
{
|
||||
$this->artifacts()->save($artifact);
|
||||
}
|
||||
}
|
||||
|
40
app/Notification/Notification.php
Normal file
40
app/Notification/Notification.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notification;
|
||||
|
||||
use App\Models\Job;
|
||||
use App\Notification\Stringifiable\StringifiableSimpleText;
|
||||
|
||||
abstract class Notification {
|
||||
|
||||
protected Job $job;
|
||||
private NotificationBody $body;
|
||||
|
||||
public bool $isError;
|
||||
|
||||
public function __construct(int $jobId, NotificationBody $body, bool $isError = false) {
|
||||
$this->job = Job::find($jobId);
|
||||
$this->body = $body;
|
||||
$this->isError = $isError;
|
||||
}
|
||||
|
||||
public function getTitle(): Stringifiable {
|
||||
return new StringifiableSimpleText($this->job->name);
|
||||
}
|
||||
|
||||
public function getBody(): Stringifiable {
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
abstract public function getLinkURL(): ?string;
|
||||
|
||||
public function getImageURL(): ?string {
|
||||
$imageProjectPath = $this->getImageProjectPath();
|
||||
if ($imageProjectPath === null) {
|
||||
return null;
|
||||
}
|
||||
return url($imageProjectPath);
|
||||
}
|
||||
|
||||
abstract public function getImageProjectPath(): ?string;
|
||||
}
|
8
app/Notification/NotificationBody.php
Normal file
8
app/Notification/NotificationBody.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notification;
|
||||
|
||||
use App\Notification\Stringifiable;
|
||||
|
||||
abstract class NotificationBody extends Stringifiable {
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notification\NotificationBody\Hellcase;
|
||||
|
||||
use App\Browser\Jobs\Hellcase\HellcaseLoginQrCode;
|
||||
use App\Notification\NotificationBody;
|
||||
|
||||
class HellcaseNotificationDailyFreeBody extends NotificationBody {
|
||||
|
||||
private string $content = "Vous avez remporté un cadeau gratuit sur Hellcase !";
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function toMarkdownString(): string {
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function toString(): string {
|
||||
return $this->content;
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notification\NotificationBody\Hellcase;
|
||||
|
||||
use App\Browser\Jobs\Hellcase\HellcaseLoginQrCode;
|
||||
use App\Notification\NotificationBody;
|
||||
|
||||
class HellcaseNotificationLoginBody extends NotificationBody {
|
||||
|
||||
private string $content = "Veuillez utiliser steam guard pour vous connecter à Hellcase.\nLe QR code se rafrachira toutes les ". HellcaseLoginQrCode::QR_CODE_VALIDITY ." secondes.";
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function toMarkdownString(): string {
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function toString(): string {
|
||||
return $this->content;
|
||||
}
|
||||
}
|
44
app/Notification/NotificationBody/ListNotificationBody.php
Normal file
44
app/Notification/NotificationBody/ListNotificationBody.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notification\NotificationBody;
|
||||
|
||||
use App\Notification\NotificationBody;
|
||||
use App\Notification\Stringifiable;
|
||||
|
||||
class ListNotificationBody extends NotificationBody {
|
||||
|
||||
private array $content;
|
||||
|
||||
public function __construct(array $content) {
|
||||
$this->content = $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function toMarkdownString(): string {
|
||||
$string = "";
|
||||
foreach ($this->content as $item) {
|
||||
$string .= "- ". $this->getTextFromContent($item) . "\n";
|
||||
}
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function toString(): string {
|
||||
$string = "";
|
||||
foreach ($this->content as $item) {
|
||||
$string .= $this->getTextFromContent($item) . "\n";
|
||||
}
|
||||
return $string;
|
||||
}
|
||||
|
||||
public function getTextFromContent(string|Stringifiable $content): string {
|
||||
if ($content instanceof Stringifiable) {
|
||||
return $content->toString();
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
}
|
32
app/Notification/NotificationBody/SimpleNotificationBody.php
Normal file
32
app/Notification/NotificationBody/SimpleNotificationBody.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notification\NotificationBody;
|
||||
|
||||
use App\Notification\NotificationBody;
|
||||
use App\Notification\Stringifiable;
|
||||
|
||||
class SimpleNotificationBody extends NotificationBody {
|
||||
|
||||
private string $body;
|
||||
|
||||
public function __construct(string $body) {
|
||||
$this->body = $body;
|
||||
}
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function toMarkdownString(): string {
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
public function toHTMLString(): string {
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function toString(): string {
|
||||
return $this->body;
|
||||
}
|
||||
}
|
7
app/Notification/NotificationProvider.php
Normal file
7
app/Notification/NotificationProvider.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notification;
|
||||
|
||||
abstract class NotificationProvider {
|
||||
abstract public static function send(Notification $notification): void;
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notification\Notifications\Hellcase;
|
||||
|
||||
use App\Browser\Jobs\Hellcase\HellcaseDailyFreeScreenshot;
|
||||
use App\Browser\Jobs\Hellcase\HellcaseLoginQrCode;
|
||||
use App\Notification\Notification;
|
||||
use App\Notification\Notifications\NotificationLogin;
|
||||
use Laravel\Dusk\Browser;
|
||||
|
||||
class HellcaseNotificationDailyFree extends NotificationLogin {
|
||||
|
||||
public function __construct(int $jobId, \App\Notification\NotificationBody $body) {
|
||||
parent::__construct($jobId, $body);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getImageProjectPath(): string|null {
|
||||
return HellcaseDailyFreeScreenshot::getImgFileProjectPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getLinkURL(): string|null {
|
||||
return route('jobs.show', ['job' => $this->job->id]);
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notification\Notifications\Hellcase;
|
||||
|
||||
use App\Browser\Jobs\Hellcase\HellcaseLoginQrCode;
|
||||
use App\Notification\Notification;
|
||||
use App\Notification\Notifications\NotificationLogin;
|
||||
use Laravel\Dusk\Browser;
|
||||
|
||||
class HellcaseNotificationLogin extends NotificationLogin {
|
||||
|
||||
public function __construct(int $jobId, \App\Notification\NotificationBody $body) {
|
||||
parent::__construct($jobId, $body);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getImageProjectPath(): string|null {
|
||||
return HellcaseLoginQrCode::getImgFileProjectPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getLinkURL(): string|null {
|
||||
return route('jobs.show', ['job' => $this->job->id]);
|
||||
}
|
||||
}
|
11
app/Notification/Notifications/NotificationLogin.php
Normal file
11
app/Notification/Notifications/NotificationLogin.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notification\Notifications;
|
||||
|
||||
use App\Notification\Notification;
|
||||
|
||||
abstract class NotificationLogin extends Notification {
|
||||
public function __construct(int $jobId, \App\Notification\NotificationBody $body) {
|
||||
parent::__construct($jobId, $body, true);
|
||||
}
|
||||
}
|
36
app/Notification/Notifications/SimpleNotification.php
Normal file
36
app/Notification/Notifications/SimpleNotification.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notification\Notifications;
|
||||
|
||||
use App\Notification\Notification;
|
||||
use App\Notification\NotificationBody\SimpleNotificationBody;
|
||||
use App\Notification\Stringifiable;
|
||||
use App\Notification\Stringifiable\StringifiableSimpleText;
|
||||
|
||||
class SimpleNotification extends Notification {
|
||||
|
||||
private StringifiableSimpleText $title;
|
||||
|
||||
public function __construct(int $jobId, string $title, string $body, bool $isError = false) {
|
||||
$this->title = new StringifiableSimpleText($title);
|
||||
parent::__construct($jobId, new SimpleNotificationBody($body), $isError);
|
||||
}
|
||||
|
||||
public function getTitle(): Stringifiable {
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getImageProjectPath(): string|null {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getLinkURL(): string|null {
|
||||
return route('home');
|
||||
}
|
||||
}
|
23
app/Notification/Providers/AllNotification.php
Normal file
23
app/Notification/Providers/AllNotification.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notification\Providers;
|
||||
|
||||
use App\Notification\NotificationProvider;
|
||||
use App\Notification\INotificationProvider;
|
||||
use App\Models\JobInfo;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class AllNotification extends NotificationProvider {
|
||||
private const NOTIFICATIONS_PROVIDERS = [
|
||||
DiscordWebHookNotification::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function send(\App\Notification\Notification $notification): void {
|
||||
foreach (self::NOTIFICATIONS_PROVIDERS as $provider) {
|
||||
$provider::send($notification);
|
||||
}
|
||||
}
|
||||
}
|
110
app/Notification/Providers/DiscordWebHookNotification.php
Normal file
110
app/Notification/Providers/DiscordWebHookNotification.php
Normal file
@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notification\Providers;
|
||||
|
||||
use App\Notification\NotificationProvider;
|
||||
use App\Notification\INotificationProvider;
|
||||
use App\Models\JobInfo;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class DiscordWebHookNotification extends NotificationProvider {
|
||||
|
||||
private const EMBED_COLOR = ["521254", "16058119"];
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function send(\App\Notification\Notification $notification): void {
|
||||
/*
|
||||
Test Json for a complete embed :
|
||||
{
|
||||
"content": "",
|
||||
"tts": false,
|
||||
"embeds": [
|
||||
{
|
||||
"id": 652627557,
|
||||
"title": "Title",
|
||||
"description": "This is the markdown body\n",
|
||||
"color": 521254,
|
||||
"fields": [
|
||||
{
|
||||
"id": 984079152,
|
||||
"name": "Field 1",
|
||||
"value": "test"
|
||||
}
|
||||
],
|
||||
"url": "https://localhost:8000",
|
||||
"image": {
|
||||
"url": "https://www.thoughtco.com/thmb/jzJO77P0K9zIbqxQOVOaHWFCfj4=/1732x1732/filters:fill(auto,1)/GettyImages-186451154-58c3965a3df78c353cf8cc7b.jpg"
|
||||
}
|
||||
}
|
||||
],
|
||||
"components": [],
|
||||
"actions": {},
|
||||
"username": "Datboi",
|
||||
"avatar_url": "https://www.fairytailrp.com/t40344-here-come-dat-boi"
|
||||
}
|
||||
*/
|
||||
$webHookUrl = static::getDiscordWebHookUrl($notification->isError);
|
||||
$body = [
|
||||
"content"=> "",
|
||||
"tts"=> false,
|
||||
"embeds" => [
|
||||
[
|
||||
"id" => 652627557,
|
||||
"title" => $notification->getTitle()->toString(),
|
||||
"description" => $notification->getBody()->toMarkdownString(),
|
||||
"color" => self::EMBED_COLOR[(int)$notification->isError],
|
||||
"url" => $notification->getLinkURL(),
|
||||
],
|
||||
],
|
||||
"username" => "Datboi",
|
||||
"avatar_url" => "https://media1.giphy.com/media/yDTWAecZcB2Jq/200w.gif?cid=6c09b952f68kz3wnkqsmyha8e7xrpe8n2kx0nkf2b8cir6am&rid=200w.gif&ct=g",
|
||||
];
|
||||
|
||||
if ($notification->getImageURL() !== null) {
|
||||
$body["embeds"][0]["image"] = [
|
||||
"url" => "attachment://image.png"
|
||||
];
|
||||
}
|
||||
|
||||
$payloadJson = json_encode($body);
|
||||
|
||||
$formData = [
|
||||
'payload_json' => $payloadJson,
|
||||
];
|
||||
|
||||
if ($notification->getImageURL() !== null) {
|
||||
$formData['file'] = curl_file_create($notification->getImageProjectPath(), 'image/png', 'image.png');
|
||||
}
|
||||
|
||||
$ch = curl_init($webHookUrl);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-type: multipart/form-data'));
|
||||
curl_setopt($ch, CURLOPT_POST, 1);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $formData);
|
||||
// curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
$response = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
}
|
||||
|
||||
private static function getDiscordWebHookUrl(bool $isError): string {
|
||||
$generalWebHookUrlKey = 'discord_webhook_url';
|
||||
$generalWebHookUrl = Cache::rememberForever($generalWebHookUrlKey, function () use ($generalWebHookUrlKey) {
|
||||
return JobInfo::where('key', $generalWebHookUrlKey)->first()->value;
|
||||
});
|
||||
if ($generalWebHookUrl === null) {
|
||||
throw new \Exception("Le webhook discord n'a pas été configuré");
|
||||
}
|
||||
|
||||
if ($isError) {
|
||||
$errorWebHookUrlKey = 'discord_error_webhook_url';
|
||||
$errorWebHookUrl = Cache::rememberForever($errorWebHookUrlKey, function () use ($errorWebHookUrlKey) {
|
||||
return JobInfo::where('key', $errorWebHookUrlKey)->first()->value;
|
||||
});
|
||||
return $errorWebHookUrl ?? $generalWebHookUrl;
|
||||
}
|
||||
else {
|
||||
return $generalWebHookUrl;
|
||||
}
|
||||
}
|
||||
}
|
21
app/Notification/Stringifiable.php
Normal file
21
app/Notification/Stringifiable.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notification;
|
||||
|
||||
use Parsedown;
|
||||
|
||||
abstract class Stringifiable{
|
||||
private Parsedown $parsedown;
|
||||
|
||||
public function __construct() {
|
||||
$this->parsedown = new Parsedown();
|
||||
}
|
||||
|
||||
abstract public function toString(): string;
|
||||
|
||||
public function toHTMLString(): string {
|
||||
return $this->parsedown->text($this->toMarkdownString());
|
||||
}
|
||||
|
||||
abstract public function toMarkdownString(): string;
|
||||
}
|
34
app/Notification/Stringifiable/StringifiableSimpleText.php
Normal file
34
app/Notification/Stringifiable/StringifiableSimpleText.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notification\Stringifiable;
|
||||
|
||||
use App\Notification\Stringifiable;
|
||||
|
||||
class StringifiableSimpleText extends Stringifiable {
|
||||
private string $text;
|
||||
|
||||
public function __construct(string $text) {
|
||||
$this->text = $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function toHTMLString(): string {
|
||||
return $this->text;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function toMarkdownString(): string {
|
||||
return $this->text;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function toString(): string {
|
||||
return $this->text;
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Browser\BrowserJob;
|
||||
use Illuminate\Support\Facades\Vite;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
|
37
app/Providers/BrowserJobsServiceProvider.php
Normal file
37
app/Providers/BrowserJobsServiceProvider.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Services\BrowserJobsInstances;
|
||||
use Illuminate\Contracts\Foundation\Application;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class BrowserJobsServiceProvider extends ServiceProvider
|
||||
{
|
||||
|
||||
private BrowserJobsInstances $service;
|
||||
|
||||
public function __construct(Application $app)
|
||||
{
|
||||
parent::__construct($app);
|
||||
$this->service = new BrowserJobsInstances();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
$this->app->instance(BrowserJobsInstances::class, $this->service);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
$this->app->terminating(function (BrowserJobsInstances $instances) {
|
||||
$instances->terminateAll();
|
||||
});
|
||||
}
|
||||
}
|
92
app/Services/BrowserJobsInstances.php
Normal file
92
app/Services/BrowserJobsInstances.php
Normal file
@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Browser\BrowserJob;
|
||||
use Cache;
|
||||
use Exception;
|
||||
|
||||
class BrowserJobsInstances {
|
||||
|
||||
/**
|
||||
* A dictionnary of all job instances by their jobId
|
||||
* @var array Dictionnary of BrowserJob by their jobId
|
||||
*/
|
||||
private $jobInstancesByJobId = [];
|
||||
|
||||
/**
|
||||
* Index all jobs in the app/Browser/Jobs directory
|
||||
* and store them in the cache
|
||||
* @return void
|
||||
*/
|
||||
private function indexJobsClassesById(): void
|
||||
{
|
||||
// Read all directories in app/Browser/jobs,
|
||||
// foreach directory, get the file named {directoryName}Job.php if it exists
|
||||
$folders = scandir(app_path('Browser/Jobs'));
|
||||
$files = [];
|
||||
foreach ($folders as $folder) {
|
||||
if ($folder == '.' || $folder == '..') {
|
||||
continue;
|
||||
}
|
||||
if (is_dir(app_path('Browser/Jobs/' . $folder))) {
|
||||
$potentialFileName = $folder . '/' . $folder . 'Job.php';
|
||||
if (file_exists(app_path('Browser/Jobs/' . $potentialFileName))) {
|
||||
$files[] = $potentialFileName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make a dictionnary of the id of the job as the key and the job class instance as value
|
||||
foreach ($files as $file) {
|
||||
$className = str_replace('.php', '', $file);
|
||||
$className = str_replace('/', '\\', $className);
|
||||
$fullClassName = 'App\Browser\Jobs\\' . $className;
|
||||
$jobInstance = new $fullClassName();
|
||||
$jobId = $jobInstance->jobId;
|
||||
Cache::put('jobClass' . $jobId, $fullClassName); // Met le nom de la classe en cache
|
||||
$this->jobInstancesByJobId[$jobId] = $jobInstance;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an instance of a job by it's jobId
|
||||
* @param mixed $jobId The ID of the job in the database
|
||||
* @return BrowserJob
|
||||
*/
|
||||
public function getJobInstance($jobId): BrowserJob
|
||||
{
|
||||
try {
|
||||
return $this->jobInstancesByJobId[$jobId];
|
||||
} catch (Exception $e) {
|
||||
return $this->getNewJobInstance($jobId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance of a job class
|
||||
* @param int $jobId
|
||||
* @return object
|
||||
*/
|
||||
private function getNewJobInstance(int $jobId): BrowserJob {
|
||||
$jobClass = Cache::get('jobClass' . $jobId);
|
||||
if ($jobClass == null) { // If we don't have the class in cache, we put all of the job in cache
|
||||
$this->indexJobsClassesById();
|
||||
return $this->jobInstancesByJobId[$jobId]; // indexJobsClassesById() already created an instance of the job
|
||||
}
|
||||
// If we have the class in cache, we create a new instance of it
|
||||
$instance = new $jobClass();
|
||||
$this->jobInstancesByJobId[$jobId] = $instance;
|
||||
return $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Terminate all jobs
|
||||
* @return void
|
||||
*/
|
||||
public function terminateAll() {
|
||||
foreach ($this->jobInstancesByJobId as $jobInstance) {
|
||||
$jobInstance->terminate();
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user