use App\Models\DiscordBotCommand;
use App\Models\DiscordGuild;
+use App\Models\Episode;
use App\Models\Round;
use App\Models\User;
use Discord\Discord;
use Discord\Parts\User\Member;
use Discord\Parts\User\User as DiscordUser;
use Illuminate\Support\Facades\App;
+use React\Promise\PromiseInterface;
abstract class BaseCommand {
- public static function resolve(Discord $discord, DiscordBotCommand $cmd) {
+ public static function resolve(Discord $discord, DiscordBotCommand $cmd): BaseCommand {
switch ($cmd->command) {
+ case 'episode-event':
+ return new EpisodeEventCommand($discord, $cmd);
case 'message':
return new MessageCommand($discord, $cmd);
case 'presence':
case 'sync-user':
return new SyncUserCommand($discord, $cmd);
default:
- throw new Exception('unrecognized command');
+ throw new \Exception('unrecognized command');
}
}
- public abstract function execute();
+ abstract public function execute(): PromiseInterface;
protected function __construct(Discord $discord, DiscordBotCommand $cmd) {
}
}
- protected function fetchGuild() {
+ protected function fetchGuild(): PromiseInterface {
if (isset($this->guild)) {
return \React\Promise\resolve($this->guild);
}
- if (is_null($this->command->discord_guild)) {
- $g = DiscordGuild::where('guild_id', '=', $this->command->tournament->discord)->firstOrFail();
- $this->command->discord_guild()->associate($g);
- $this->command->save();
- }
+ $cmd_guild = $this->getCommandGuild();
return $this->discord->guilds
- ->fetch($this->command->discord_guild->guild_id)
+ ->fetch($cmd_guild->guild_id)
->then(function (Guild $guild) {
$this->guild = $guild;
if ($guild->preferred_locale && !($this->command->tournament && $this->command->tournament->locale)) {
});
}
- protected function fetchMember() {
+ protected function getCommandGuild(): DiscordGuild {
+ if (is_null($this->command->discord_guild)) {
+ $g = DiscordGuild::where('guild_id', '=', $this->command->tournament->discord)->firstOrFail();
+ $this->command->discord_guild()->associate($g);
+ $this->command->save();
+ }
+ return $this->command->discord_guild;
+ }
+
+ protected function fetchMember(): PromiseInterface {
return $this->fetchGuild()->then(function (Guild $guild) {
return $guild->members
->fetch($this->getParameter('user'))
});
}
- protected function fetchParameterChannel() {
+ protected function fetchParameterChannel(): PromiseInterface {
if (isset($this->parameterChannel)) {
return \React\Promise\resolve($this->parameterChannel);
}
});
}
- protected function fetchRoundChannel() {
+ protected function fetchRoundChannel(): PromiseInterface {
if (isset($this->roundChannel)) {
return \React\Promise\resolve($this->roundChannel);
}
});
}
- protected function fetchUser() {
+ protected function fetchUser(): PromiseInterface {
if (isset($this->user)) {
return \React\Promise\resolve($this->user);
}
return $this->command->parameters[$name];
}
- protected function getRound() {
+ protected function getEpisode(): Episode {
+ if (!$this->hasParameter('episode')) {
+ throw new \Exception('no episode in parameters');
+ }
+ return Episode::findOrFail($this->getParameter('episode'));
+ }
+
+ protected function getRound(): Round {
if (!$this->hasParameter('round')) {
- throw new \Exception('no rounds in parameters');
+ throw new \Exception('no round in parameters');
}
return Round::findOrFail($this->getParameter('round'));
}
--- /dev/null
+<?php
+
+namespace App\DiscordBotCommands;
+
+use App\Models\DiscordBotCommand;
+use App\Models\DiscordGuildEpisode;
+use App\Models\Episode;
+use Carbon\Carbon;
+use Discord\Discord;
+use Discord\Parts\Guild\Guild;
+use Discord\Parts\Guild\ScheduledEvent;
+use React\Promise\PromiseInterface;
+
+class EpisodeEventCommand extends BaseCommand {
+
+ public function __construct(Discord $discord, DiscordBotCommand $cmd) {
+ parent::__construct($discord, $cmd);
+ }
+
+ public function execute(): PromiseInterface {
+ return $this->fetchGuild()
+ ->then(function (Guild $guild) {
+ $episode = $this->getEpisode();
+ $memo = $this->getMemo($episode);
+ if ($memo->scheduled_event_id) {
+ return $guild->guild_scheduled_events
+ ->fetch($memo->scheduled_event_id)
+ ->then(function ($event) use ($episode, $guild, $memo) {
+ $this->configureEvent($event, $episode);
+ $guild->guild_scheduled_events
+ ->save($event)
+ ->then(function (ScheduledEvent $savedEvent) use ($memo) {
+ $memo->synced_at = now();
+ $memo->save();
+ });
+ });
+ } else {
+ $event = $guild->guild_scheduled_events->create();
+ $this->configureEvent($event, $episode);
+ return $guild->guild_scheduled_events
+ ->save($event)
+ ->then(function (ScheduledEvent $createdEvent) use ($memo) {
+ $memo->scheduled_event_id = $createdEvent->id;
+ $memo->synced_at = now();
+ $memo->save();
+ });
+ }
+ });
+ }
+
+ private function getMemo(Episode $episode): DiscordGuildEpisode {
+ return DiscordGuildEpisode::firstOrCreate([
+ 'discord_guild_id' => $this->getCommandGuild()->id,
+ 'episode_id' => $episode->id,
+ ]);
+ }
+
+ private function configureEvent(ScheduledEvent $event, Episode $episode): void {
+ $end = Carbon::parse($episode->start)->add($episode->estimate, 'seconds')->toIso8601String();
+ $event->name = $episode->getScheduledEventName();
+ $event->description = $episode->getScheduledEventDescription();
+ $event->scheduled_start_time = $episode->start;
+ $event->scheduled_end_time = $end;
+ $event->privacy_level = ScheduledEvent::PRIVACY_LEVEL_GUILD_ONLY;
+ $event->entity_type = ScheduledEvent::ENTITY_TYPE_EXTERNAL;
+ $event->entity_metadata = [
+ 'location' => $episode->getRestreamLink(),
+ ];
+ $event->image = '';
+ }
+
+}
use App\Models\ChatBotLog;
use App\Models\ChatLog;
-use Carbon\Carbon;
use Illuminate\Http\Request;
class ChatBotLogController extends Controller {
return $this->belongsTo(User::class);
}
- public function execute(Discord $discord) {
+ public function execute(Discord $discord): void {
$this->setExecuting();
try {
BaseCommand::resolve($discord, $this)
->execute()
- ->done(function($v = null) {
+ ->done(function ($v = null) {
$this->setDone();
}, function (\Throwable $e) {
$this->setException($e);
--- /dev/null
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Model;
+
+class DiscordGuildEpisode extends Model {
+
+ public function discord_guild() {
+ return $this->belongsTo(DiscordGuild::class);
+ }
+
+ public function episode() {
+ return $this->belongsTo(Episode::class);
+ }
+
+ protected $fillable = [
+ 'discord_guild_id',
+ 'episode_id',
+ ];
+
+}
return $this->hasMany(EpisodePlayer::class);
}
+ public function getScheduledEventName(): string {
+ $parts = [];
+ if (count($this->players) > 1) {
+ $players = [];
+ foreach ($this->players as $player) {
+ $players[] = $player->getName();
+ }
+ $parts[] = implode(' vs ', $players);
+ }
+ if ($this->title != '') {
+ $parts[] = $this->title;
+ }
+ if ($this->event->title != '') {
+ $parts[] = $this->event->title;
+ }
+ return implode(' - ', $parts);
+ }
+
+ public function getScheduledEventDescription(): string {
+ if ($this->comment != '') {
+ return $this->comment;
+ }
+ return '';
+ }
+
+ public function getRestreamLink(string $preferred_lang = ''): string {
+ if (count($this->channels) > 0) {
+ foreach ($this->channels as $channel) {
+ if (in_array($preferred_lang, $channel->languages)) {
+ return $channel->stream_link;
+ }
+ }
+ return $this->channels[0]->stream_link;
+ }
+ if (count($this->players) == 1) {
+ return $this->players[0]->getStreamLink();
+ }
+ if (count($this->players) > 0) {
+ $link = 'https://multistre.am';
+ foreach ($this->players as $player) {
+ $link .= '/'.$player->getTwitchName();
+ }
+ return $link;
+ }
+ return '';
+ }
+
protected $casts = [
'confirmed' => 'boolean',
'start' => 'datetime',
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
+use Illuminate\Support\Str;
class EpisodePlayer extends Model
{
return '';
}
- public function getStreamLink() {
+ public function getStreamLink(): string {
if (!empty($this->stream_override)) {
return $this->stream_override;
}
return '';
}
+ public function getTwitchName(): string {
+ return Str::afterLast($this->getStreamLink(), '/');
+ }
+
protected $casts = [
'user_id' => 'string',
];
--- /dev/null
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+ /**
+ * Run the migrations.
+ */
+ public function up(): void {
+ Schema::create('discord_guild_episodes', function (Blueprint $table) {
+ $table->id();
+ $table->foreignId('discord_guild_id')->constrained();
+ $table->foreignId('episode_id')->constrained();
+ $table->string('scheduled_event_id')->nullable()->default(null);
+ $table->timestamp('synced_at')->nullable()->default(null);
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void {
+ Schema::dropIfExists('discord_guild_episodes');
+ }
+};