From ca90048a9fb2d8c3c65d81096ecd36bbf7c51ff5 Mon Sep 17 00:00:00 2001
From: Daniel Karbach
Date: Tue, 10 May 2022 19:10:37 +0200
Subject: [PATCH] aos seed generation
---
.env.example | 1 +
app/DiscordAppCommands/AosrPresetCommand.php | 73 +++----
app/Jobs/GenerateAosSeed.php | 80 ++++++++
app/Models/AosSeed.php | 21 ++
composer.json | 1 +
composer.lock | 189 +++++++++++++++++-
config/aos.php | 2 +
.../2022_05_10_144912_aos_seed_generation.php | 34 ++++
resources/js/components/aos/Seed.js | 35 +++-
resources/js/components/pages/AosSeed.js | 31 ++-
resources/js/i18n/de.js | 9 +
resources/js/i18n/en.js | 9 +
12 files changed, 442 insertions(+), 43 deletions(-)
create mode 100644 app/Jobs/GenerateAosSeed.php
create mode 100644 database/migrations/2022_05_10_144912_aos_seed_generation.php
diff --git a/.env.example b/.env.example
index 9fa47a0..c1b602f 100644
--- a/.env.example
+++ b/.env.example
@@ -61,5 +61,6 @@ DISCORD_BOT_TOKEN=
DISCORD_BOT_CREATE_COMMANDS=
DISCORD_BOT_ENABLE_COMMANDS=
+AOS_BASE_ROM=
AOS_HOSTNAME=aos.localhorst.tv
AOS_URL=https://aos.localhorst.tv
diff --git a/app/DiscordAppCommands/AosrPresetCommand.php b/app/DiscordAppCommands/AosrPresetCommand.php
index 8ec3eb4..a023fcb 100644
--- a/app/DiscordAppCommands/AosrPresetCommand.php
+++ b/app/DiscordAppCommands/AosrPresetCommand.php
@@ -2,6 +2,7 @@
namespace App\DiscordAppCommands;
+use App\Models\AosSeed;
use Discord\Builders\MessageBuilder;
use Discord\Discord;
use Discord\Parts\Embed\Embed;
@@ -22,15 +23,15 @@ class AosrPresetCommand {
$cmd = $discord->application->commands->create([
'name' => 'aosr',
'type' => 1,
- 'description' => '(testing) Generate an AoSRando seed.',
+ 'description' => 'Generate an AoSRando seed.',
'description_localizations' => [
- 'de' => '(test) Generiert einen AoSRando Seed.',
+ 'de' => 'Generiert einen AoSRando Seed.',
],
'options' => [[
'name' => 'preset',
- 'description' => '(testing) Generate an AoSRando seed from preset.',
+ 'description' => 'Generate an AoSRando seed from preset.',
'description_localizations' => [
- 'de' => '(test) Generiert einen AoSRando Seed anhand eines Presets.',
+ 'de' => 'Generiert einen AoSRando Seed anhand eines Presets.',
],
'type' => 1,
'options' => [[
@@ -50,36 +51,40 @@ class AosrPresetCommand {
public static function listen(Discord $discord) {
$discord->listenCommand(['aosr', 'preset'], function(Interaction $interaction) use ($discord) {
- $presetName = $interaction->data->options['preset']->options['preset']->value;
- $message = MessageBuilder::new();
- if (isset(static::$presets[$presetName])) {
- $preset = static::$presets[$presetName];
- $seed = strval(random_int(-2147483648, 2147483647));
- $params = array_merge(['seed' => $seed], $preset['settings']);
- $url = 'https://aosrando.surge.sh/?'.http_build_query($params, '', '&');
- $embed = new Embed($discord, [
- 'fields' => [
- new Field($discord, [ 'name' => 'Preset', 'value' => $preset['name'], 'inline' => true ]),
- new Field($discord, [ 'name' => 'Seed', 'value' => $seed, 'inline' => true ]),
- new Field($discord, [ 'name' => 'Logic', 'value' => $preset['settings']['logic'], 'inline' => true ]),
- new Field($discord, [ 'name' => 'Area', 'value' => $preset['settings']['area'], 'inline' => true ]),
- new Field($discord, [ 'name' => 'Boss', 'value' => $preset['settings']['boss'], 'inline' => true ]),
- new Field($discord, [ 'name' => 'Enemy', 'value' => $preset['settings']['enemy'], 'inline' => true ]),
- new Field($discord, [ 'name' => 'Link', 'value' => $url ]),
- ],
- 'footer' => new Footer($discord, [
- 'text' => 'Grün',
- ]),
- 'timestamp' => now(),
- 'title' => 'AoSRando Seed',
- 'type' => 'rich',
- 'url' => $url,
- ]);
- $message->addEmbed($embed);
- } else {
- $message->setContent('unknown preset '.$presetName);
- }
- $interaction->respondWithMessage($message);
+ $interaction
+ ->acknowledgeWithResponse()
+ ->done(function() use($discord, $interaction) {
+ $presetName = $interaction->data->options['preset']->options['preset']->value;
+ $message = MessageBuilder::new();
+ if (isset(static::$presets[$presetName])) {
+ $preset = static::$presets[$presetName];
+ $seed = AosSeed::generateSurge($presetName, $preset['settings']);
+
+ $embed = new Embed($discord, [
+ 'fields' => [
+ new Field($discord, [ 'name' => 'Generator', 'value' => 'This seed has been generated with fusecv\'s randomizer on aosrando.surge.sh.' ]),
+ new Field($discord, [ 'name' => 'Preset', 'value' => $preset['name'], 'inline' => true ]),
+ new Field($discord, [ 'name' => 'Seed', 'value' => $seed->seed, 'inline' => true ]),
+ new Field($discord, [ 'name' => 'Logic', 'value' => $preset['settings']['logic'], 'inline' => true ]),
+ new Field($discord, [ 'name' => 'Area', 'value' => $preset['settings']['area'], 'inline' => true ]),
+ new Field($discord, [ 'name' => 'Boss', 'value' => $preset['settings']['boss'], 'inline' => true ]),
+ new Field($discord, [ 'name' => 'Enemy', 'value' => $preset['settings']['enemy'], 'inline' => true ]),
+ new Field($discord, [ 'name' => 'Permalink', 'value' => $seed->permalink ]),
+ ],
+ 'footer' => new Footer($discord, [
+ 'text' => 'Grün',
+ ]),
+ 'timestamp' => now(),
+ 'title' => 'AoSRando Seed',
+ 'type' => 'rich',
+ 'url' => $seed->permalink,
+ ]);
+ $message->addEmbed($embed);
+ } else {
+ $message->setContent('unknown preset '.$presetName);
+ }
+ $interaction->updateOriginalResponse($message);
+ });
return true;
});
}
diff --git a/app/Jobs/GenerateAosSeed.php b/app/Jobs/GenerateAosSeed.php
new file mode 100644
index 0000000..b8c7e9e
--- /dev/null
+++ b/app/Jobs/GenerateAosSeed.php
@@ -0,0 +1,80 @@
+seed = $seed;
+ }
+
+ /**
+ * Execute the job.
+ *
+ * @return void
+ */
+ public function handle()
+ {
+ try {
+ $temp_dir = sys_get_temp_dir();
+
+ $seed = $this->seed;
+ $params = array_merge(['seed' => $seed->seed], $seed->settings);
+ $url = 'https://aosrando.surge.sh/?'.http_build_query($params, '', '&');
+
+ $fac = new BrowserFactory('chromium');
+ $browser = $fac->createBrowser();
+
+ $page = $browser->createPage();
+ $page->setDownloadPath($temp_dir);
+ $page->navigate($url)->waitForNavigation();
+
+ $fileInput = $page->dom()->querySelector('input[type=file]');
+ $fileInput->sendFile(config('aos.base_rom'));
+ $page->waitUntilContainsElement('select');
+
+ $page->mouse()->find('button', 2)->click();
+ $page->waitUntilContainsElement('a[download]');
+
+ $page->dom()->querySelector('a[download]')->setAttributeValue('download', $seed->hash.'.gba');
+ $page->mouse()->find('a[download]')->click();
+
+ sleep(2);
+
+ $encoder = new Encoder(file_get_contents(config('aos.base_rom')));
+ $patch = $encoder->createPatch(file_get_contents($temp_dir.'/'.$seed->hash.'.gba'));
+ Storage::disk('aos-seeds')->put($seed->hash.'.bps', $patch);
+ unlink($temp_dir.'/'.$seed->hash.'.gba');
+
+ $seed->status = 'generated';
+ $seed->save();
+
+ $browser->close();
+ } catch (\Throwable) {
+ $seed->status = 'error';
+ $seed->save();
+ }
+ }
+
+ protected $seed;
+
+}
diff --git a/app/Models/AosSeed.php b/app/Models/AosSeed.php
index d44cfcc..18633b7 100644
--- a/app/Models/AosSeed.php
+++ b/app/Models/AosSeed.php
@@ -2,13 +2,34 @@
namespace App\Models;
+use App\Jobs\GenerateAosSeed;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
+use Illuminate\Support\Str;
class AosSeed extends Model
{
use HasFactory;
+ public static function generateSurge($preset, $settings, $race = false, $mystery = false) {
+ $seed = new static();
+ $seed->hash = Str::random(16);
+ $seed->generator = 'surge';
+ $seed->preset = $preset;
+ $seed->race = $race;
+ $seed->mystery = $mystery;
+ $seed->seed = strval(random_int(-2147483648, 2147483647));
+ $seed->settings = $settings;
+ $seed->status = 'pending';
+ $seed->save();
+ GenerateAosSeed::dispatch($seed)->onConnection('database');
+ return $seed;
+ }
+
+ public function getPermalinkAttribute() {
+ return config('aos.url').'/h/'.rawurlencode($this->hash);
+ }
+
protected $casts = [
'mystery' => 'boolean',
'race' => 'boolean',
diff --git a/composer.json b/composer.json
index 490fb47..06e6186 100644
--- a/composer.json
+++ b/composer.json
@@ -7,6 +7,7 @@
"require": {
"php": "^8.0.2",
"beyondcode/laravel-websockets": "^1.13",
+ "chrome-php/chrome": "^1.6",
"doctrine/dbal": "^3.3",
"guzzlehttp/guzzle": "^7.2",
"jakyeru/larascord": "^3.0",
diff --git a/composer.lock b/composer.lock
index 560b190..3c72e90 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "7ae17dd5730b055eb0b260e5b8ee41d5",
+ "content-hash": "ac83dea6ac9ea66e7899d97abf35b4fb",
"packages": [
{
"name": "beyondcode/laravel-websockets",
@@ -211,6 +211,130 @@
},
"time": "2021-12-14T00:20:41+00:00"
},
+ {
+ "name": "chrome-php/chrome",
+ "version": "v1.6.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/chrome-php/chrome.git",
+ "reference": "e8264cb33a02053caf8877c1566e54f09c60d10c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/chrome-php/chrome/zipball/e8264cb33a02053caf8877c1566e54f09c60d10c",
+ "reference": "e8264cb33a02053caf8877c1566e54f09c60d10c",
+ "shasum": ""
+ },
+ "require": {
+ "chrome-php/wrench": "^1.2",
+ "evenement/evenement": "^3.0.1",
+ "monolog/monolog": "^1.26 || ^2.2",
+ "php": "^7.3 || ^8.0",
+ "psr/log": "^1.1 || ^2.0 || ^3.0",
+ "symfony/filesystem": "^4.4 || ^5.0 || ^6.0",
+ "symfony/polyfill-mbstring": "^1.23",
+ "symfony/process": "^4.4 || ^5.0 || ^6.0"
+ },
+ "require-dev": {
+ "bamarni/composer-bin-plugin": "^1.4.1",
+ "phpunit/phpunit": "^9.5.10",
+ "symfony/var-dumper": "^4.4 || ^5.0 || ^6.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "HeadlessChromium\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Graham Campbell",
+ "email": "hello@gjcampbell.co.uk",
+ "homepage": "https://github.com/GrahamCampbell"
+ },
+ {
+ "name": "Enrico Dias",
+ "email": "enricodias@gmail.com",
+ "homepage": "https://github.com/enricodias"
+ }
+ ],
+ "description": "Instrument headless chrome/chromium instances from PHP",
+ "keywords": [
+ "browser",
+ "chrome",
+ "chromium",
+ "crawl",
+ "headless",
+ "pdf",
+ "puppeteer",
+ "screenshot"
+ ],
+ "support": {
+ "issues": "https://github.com/chrome-php/chrome/issues",
+ "source": "https://github.com/chrome-php/chrome/tree/v1.6.0"
+ },
+ "time": "2022-03-30T09:50:16+00:00"
+ },
+ {
+ "name": "chrome-php/wrench",
+ "version": "v1.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/chrome-php/wrench.git",
+ "reference": "e8a34b54df8c9cd4f6d166bdb9df475988e17ce4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/chrome-php/wrench/zipball/e8a34b54df8c9cd4f6d166bdb9df475988e17ce4",
+ "reference": "e8a34b54df8c9cd4f6d166bdb9df475988e17ce4",
+ "shasum": ""
+ },
+ "require": {
+ "ext-sockets": "*",
+ "php": "^7.3 || ^8.0",
+ "psr/log": "^1.1 || ^2.0 || ^3.0",
+ "symfony/polyfill-php80": "^1.22"
+ },
+ "conflict": {
+ "wrench/wrench": "*"
+ },
+ "require-dev": {
+ "bamarni/composer-bin-plugin": "^1.4.1",
+ "phpunit/phpunit": "^9.5.5"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Wrench\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Graham Campbell",
+ "email": "hello@gjcampbell.co.uk",
+ "homepage": "https://github.com/GrahamCampbell"
+ }
+ ],
+ "description": "A simple PHP WebSocket implementation",
+ "keywords": [
+ "WebSockets",
+ "hybi",
+ "websocket"
+ ],
+ "support": {
+ "issues": "https://github.com/chrome-php/wrench/issues",
+ "source": "https://github.com/chrome-php/wrench/tree/v1.2.0"
+ },
+ "time": "2022-03-30T09:35:45+00:00"
+ },
{
"name": "dflydev/dot-access-data",
"version": "v3.0.1",
@@ -5486,6 +5610,69 @@
],
"time": "2022-01-02T09:55:41+00:00"
},
+ {
+ "name": "symfony/filesystem",
+ "version": "v6.0.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/filesystem.git",
+ "reference": "6c9e4c41f2c51dfde3db298594ed9cba55dbf5ff"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/filesystem/zipball/6c9e4c41f2c51dfde3db298594ed9cba55dbf5ff",
+ "reference": "6c9e4c41f2c51dfde3db298594ed9cba55dbf5ff",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.0.2",
+ "symfony/polyfill-ctype": "~1.8",
+ "symfony/polyfill-mbstring": "~1.8"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Filesystem\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Provides basic utilities for the filesystem",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/filesystem/tree/v6.0.7"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2022-04-01T12:54:51+00:00"
+ },
{
"name": "symfony/finder",
"version": "v6.0.8",
diff --git a/config/aos.php b/config/aos.php
index d093775..7f98a90 100644
--- a/config/aos.php
+++ b/config/aos.php
@@ -1,5 +1,7 @@
env('AOS_BASE_ROM', ''),
'hostname' => env('AOS_HOSTNAME', 'aos.localhorst.tv'),
+ 'url' => env('AOS_URL', 'https://aos.localhorst.tv'),
];
diff --git a/database/migrations/2022_05_10_144912_aos_seed_generation.php b/database/migrations/2022_05_10_144912_aos_seed_generation.php
new file mode 100644
index 0000000..31debf7
--- /dev/null
+++ b/database/migrations/2022_05_10_144912_aos_seed_generation.php
@@ -0,0 +1,34 @@
+string('status')->default('pending');
+ $table->unique('hash');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::table('aos_seeds', function(Blueprint $table) {
+ $table->dropColumn('status');
+ $table->dropIndex('aos_seeds_hash_unique');
+ });
+ }
+};
diff --git a/resources/js/components/aos/Seed.js b/resources/js/components/aos/Seed.js
index 289e00e..efc1130 100644
--- a/resources/js/components/aos/Seed.js
+++ b/resources/js/components/aos/Seed.js
@@ -31,7 +31,7 @@ const Seed = ({ patch, seed }) => {
{rom ?
- {i18n.t(seed.race ? 'aosSeeds.race' : 'aosSeeds.noRace')}
- {i18n.t(seed.mystery ? 'aosSeeds.mystery' : 'aosSeeds.noMystery')}
+ {seed.seed ?
+
+ {i18n.t('aosSeeds.seed')}:
+ {' '}
+ {seed.seed}
+
+ : null}
+ {seed.race ?
+ {i18n.t('aosSeeds.race')}
+ : null}
+ {seed.mystery ?
+ {i18n.t('aosSeeds.mystery')}
+ : null}
+ {seed.status === 'generated' ?
+
+ {i18n.t('aosSeeds.generated')}:
+ {' '}
+
+ {i18n.t('aosSeeds.date', { date: new Date(seed.updated_at) })}
+
+
+ :
+
+ {i18n.t('aosSeeds.status')}:
+ {' '}
+ {i18n.t(`aosSeeds.statuses.${seed.status}`)}
+
+ }
{i18n.t('aosSeeds.generator')}
@@ -71,6 +97,9 @@ Seed.propTypes = {
mystery: PropTypes.bool,
preset: PropTypes.string,
race: PropTypes.bool,
+ seed: PropTypes.string,
+ status: PropTypes.string,
+ updated_at: PropTypes.string,
}),
};
diff --git a/resources/js/components/pages/AosSeed.js b/resources/js/components/pages/AosSeed.js
index 6cea156..e3cfda8 100644
--- a/resources/js/components/pages/AosSeed.js
+++ b/resources/js/components/pages/AosSeed.js
@@ -1,5 +1,5 @@
import axios from 'axios';
-import React, { useEffect, useState } from 'react';
+import React, { useCallback, useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import NotFound from './NotFound';
@@ -17,9 +17,7 @@ const AosSeed = () => {
const [patch, setPatch] = useState(null);
const [seed, setSeed] = useState(null);
- useEffect(() => {
- setLoading(true);
- const ctrl = new AbortController();
+ const loadSeed = useCallback((hash, ctrl) => {
axios
.get(`/api/aos-seed/${hash}`, { signal: ctrl.signal })
.then(response => {
@@ -33,13 +31,36 @@ const AosSeed = () => {
setLoading(false);
setSeed(null);
});
+ }, []);
+
+ useEffect(() => {
+ setLoading(true);
+ const ctrl = new AbortController();
+ loadSeed(hash, ctrl);
return () => {
ctrl.abort();
};
}, [hash]);
+ useEffect(() => {
+ if (!seed || seed.status !== 'pending') {
+ return;
+ }
+ const ctrl = new AbortController();
+ const timer = setTimeout(() => {
+ loadSeed(seed.hash, ctrl);
+ }, 2000);
+ return () => {
+ clearTimeout(timer);
+ ctrl.abort();
+ };
+ }, [seed]);
+
useEffect(() => {
setPatch(null);
+ if (!seed || seed.status !== 'generated') {
+ return;
+ }
const ctrl = new AbortController();
axios
.get(`/aos-seeds/${hash}.bps`, {
@@ -55,7 +76,7 @@ const AosSeed = () => {
return () => {
ctrl.abort();
};
- }, [hash]);
+ }, [hash, seed]);
if (loading) {
return ;
diff --git a/resources/js/i18n/de.js b/resources/js/i18n/de.js
index 391e176..7d2aab0 100644
--- a/resources/js/i18n/de.js
+++ b/resources/js/i18n/de.js
@@ -8,9 +8,11 @@ export default {
setBaseRom: 'Base ROM auswählen',
},
aosSeeds: {
+ date: '{{ date, L LT }}',
fetchingPatch: 'Lade Patch',
filename: 'aosr - {{preset}} - {{hash}}',
heading: 'Aria of Sorrow Randomizer Seed',
+ generated: 'Generiert',
generator: 'Generator',
generators: {
surge: 'Dieser Seed wurde mit dem Randomizer von fusecv auf aosrando.surge.sh generiert',
@@ -40,6 +42,13 @@ export default {
Tournament2022: 'Turnier 2022',
},
race: 'Race',
+ seed: 'Seed',
+ status: 'Status',
+ statuses: {
+ error: 'Fehler',
+ generated: 'generiert',
+ pending: 'ausstehend',
+ },
},
applications: {
accept: 'Annehmen',
diff --git a/resources/js/i18n/en.js b/resources/js/i18n/en.js
index 190acea..4f0bcc6 100644
--- a/resources/js/i18n/en.js
+++ b/resources/js/i18n/en.js
@@ -8,9 +8,11 @@ export default {
setBaseRom: 'Set base ROM',
},
aosSeeds: {
+ date: '{{ date, L LT }}',
fetchingPatch: 'Fetching patch',
filename: 'aosr - {{preset}} - {{hash}}',
heading: 'Aria of Sorrow Randomizer Seed',
+ generated: 'Generated',
generator: 'Generator',
generators: {
surge: 'This seed has been generated with fusecv\'s randomizer on aosrando.surge.sh.',
@@ -40,6 +42,13 @@ export default {
Tournament2022: 'Tournament 2022',
},
race: 'Race',
+ seed: 'Seed',
+ status: 'Status',
+ statuses: {
+ error: 'error',
+ generated: 'generated',
+ pending: 'pending',
+ },
},
applications: {
accept: 'Accept',
--
2.39.2