Added option to customize the messages sent when the users starts streaming
This commit is contained in:
36
app/DiscordWebhookMessage.php
Normal file
36
app/DiscordWebhookMessage.php
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class DiscordWebhookMessage extends Model
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The attributes that are mass assignable.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
"enabled",
|
||||||
|
"discord_webhook_url",
|
||||||
|
"username",
|
||||||
|
"avatar",
|
||||||
|
"content",
|
||||||
|
"has_embed",
|
||||||
|
"embed_color",
|
||||||
|
"embed_author_name",
|
||||||
|
"embed_author_url",
|
||||||
|
"embed_author_icon",
|
||||||
|
"embed_thumbnail",
|
||||||
|
"embed_title",
|
||||||
|
"embed_description",
|
||||||
|
"embed_url",
|
||||||
|
"embed_image",
|
||||||
|
"embed_fields"
|
||||||
|
];
|
||||||
|
|
||||||
|
public function twitchWebook() {
|
||||||
|
return $this->morphToMany("App\TwitchWebhook", "twitch_webhook_events");
|
||||||
|
}
|
||||||
|
}
|
@@ -2,24 +2,24 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\DiscordWebhookMessage;
|
||||||
use App\TwitchWebhook;
|
use App\TwitchWebhook;
|
||||||
use App\WebhookAction;
|
use App\WebhookAction;
|
||||||
use App\Http\Requests\AddWebhookAction;
|
use App\Http\Requests\AddWebhookAction;
|
||||||
|
use App\Http\Requests\CreateDiscordWebhookMessage;
|
||||||
use App\Http\Requests\SubscribeWebhook;
|
use App\Http\Requests\SubscribeWebhook;
|
||||||
use App\Http\Requests\TestWebhookAction;
|
use App\Http\Requests\TestWebhookAction;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
use romanzipp\Twitch\Facades\Twitch;
|
use romanzipp\Twitch\Facades\Twitch;
|
||||||
use Woeler\DiscordPhp\Webhook\DiscordWebhook;
|
use Woeler\DiscordPhp\Webhook\DiscordWebhook;
|
||||||
use Woeler\DiscordPhp\Message\DiscordEmbedMessage;
|
use Woeler\DiscordPhp\Message\DiscordEmbedMessage;
|
||||||
use Woeler\DiscordPhp\Message\DiscordTextMessage;
|
use Woeler\DiscordPhp\Message\DiscordTextMessage;
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Storage;
|
|
||||||
|
|
||||||
// TODO: document all the methods/constants
|
// TODO: document all the methods/constants
|
||||||
class WebhookController extends Controller
|
class WebhookController extends Controller {
|
||||||
{
|
|
||||||
// 10 days
|
// 10 days
|
||||||
const MAX_LEASE_SECONDS = 864000;
|
const MAX_LEASE_SECONDS = 864000;
|
||||||
const TOPIC_STREAM_CHANGED = "https://api.twitch.tv/helix/streams";
|
const TOPIC_STREAM_CHANGED = "https://api.twitch.tv/helix/streams";
|
||||||
@@ -57,26 +57,41 @@ class WebhookController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: implement in a future more than one action per hook
|
// TODO: implement in a future more than one action per hook
|
||||||
public function addAction(AddWebhookAction $request) {
|
public function addAction(CreateDiscordWebhookMessage $request) {
|
||||||
$hook = Auth::user()->twitchWebhooks()->where("type", $request->type)->firstOrFail();
|
$hook = Auth::user()->twitchWebhooks()->where("type", $request->type)->firstOrFail();
|
||||||
|
|
||||||
// TODO: currently hardcoded, allow only one action
|
// currently hardcoded, allow only one action
|
||||||
$actions = $hook->webhookActions();
|
$discordWebhookMessage = $hook->discordWebhookMessage();
|
||||||
|
|
||||||
// add
|
// add
|
||||||
if (count($actions->get()) == 0) {
|
if (count($discordWebhookMessage->get()) == 0) {
|
||||||
$action = $actions->create([
|
$action = $discordWebhookMessage->create($request->all());
|
||||||
"discord_hook_url" => $request->discord_hook_url
|
|
||||||
]);
|
|
||||||
|
|
||||||
// edit the existing one
|
// edit the existing one
|
||||||
} else {
|
} else {
|
||||||
$action = $actions->first();
|
$action = $discordWebhookMessage->first();
|
||||||
$action->discord_hook_url = $request->discord_hook_url;
|
$action->fill($request->all());
|
||||||
$action->save();
|
$action->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json(["action_id" => $action->id]);
|
if ($request->hasFile("avatar")) {
|
||||||
|
if (!$request->file("avatar")->isValid()) {
|
||||||
|
// TODO display error message
|
||||||
|
abort(400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$avatarPath = $request->avatar->storeAs("discordWebhookMessages", Auth::user()->twitch_uid . "." . $request->avatar->extension(), "public");
|
||||||
|
$action->avatar = $avatarPath;
|
||||||
|
$action->save();
|
||||||
|
|
||||||
|
} else if ($request->delete_avatar && $action->avatar) {
|
||||||
|
Storage::disk("public")->delete($action->avatar);
|
||||||
|
$action->avatar = null;
|
||||||
|
$action->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(["action_id" => $action->id, "avatar" => $action->avatar ? Storage::disk("public")->url($action->avatar) : '']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -84,11 +99,11 @@ class WebhookController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function testWebhook(TestWebhookAction $request) {
|
public function testWebhook(TestWebhookAction $request) {
|
||||||
$message = new DiscordTextMessage();
|
$message = new DiscordTextMessage();
|
||||||
$message->setContent("Test message.")
|
$message->setContent($request->content ?? "Test message.")
|
||||||
->setUsername(config("app.name"))
|
->setUsername($request->username ?? config("app.name"))
|
||||||
->setAvatar(asset("img/twitch-icon.png"));
|
->setAvatar($request->avatar ?? asset("img/twitch-icon.png"));
|
||||||
|
|
||||||
$webhook = new DiscordWebhook($request->discord_hook_url);
|
$webhook = new DiscordWebhook($request->discord_webhook_url);
|
||||||
$webhook->send($message);
|
$webhook->send($message);
|
||||||
|
|
||||||
return response()->json($webhook);
|
return response()->json($webhook);
|
||||||
@@ -165,25 +180,13 @@ class WebhookController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stream change hook gets here, executes the user configured hook actions
|
* Know the stream update type sent by Twitch, returns "start", "update", or "end"
|
||||||
*/
|
*/
|
||||||
// TODO: test this, separate the 3 cases: start, update, end
|
private function streamUpdateType(TwitchWebhook $twitchWebhook, Request $request) {
|
||||||
public function streamUpdate(Request $request) {
|
|
||||||
$twitchWebhook = TwitchWebhook::findOrFail($request->hook_id);
|
|
||||||
|
|
||||||
if (!$this->verifySecret()) {
|
|
||||||
abort(403);
|
|
||||||
|
|
||||||
} else if ($twitchWebhook->disabled) {
|
|
||||||
return response("Webhook disabled", 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
$eventType = null;
|
|
||||||
$data = $request->all()["data"];
|
$data = $request->all()["data"];
|
||||||
Storage::put("stream_hook.json", json_encode($data));
|
Storage::put("stream_hook.json", json_encode($data));
|
||||||
|
|
||||||
if ($data) {
|
if ($data) {
|
||||||
|
|
||||||
$data = $data[0];
|
$data = $data[0];
|
||||||
|
|
||||||
if (empty($twitchWebhook->live_since)) {
|
if (empty($twitchWebhook->live_since)) {
|
||||||
@@ -203,10 +206,30 @@ class WebhookController extends Controller
|
|||||||
$twitchWebhook->save();
|
$twitchWebhook->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $eventType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stream change hook gets here, executes the user configured hook actions
|
||||||
|
*/
|
||||||
|
public function streamUpdate(Request $request) {
|
||||||
|
$twitchWebhook = TwitchWebhook::findOrFail($request->hook_id);
|
||||||
|
|
||||||
|
if (!$this->verifySecret()) {
|
||||||
|
abort(403);
|
||||||
|
|
||||||
|
} else if ($twitchWebhook->disabled) {
|
||||||
|
return response("Webhook disabled", 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
$eventType = $this->streamUpdateType($twitchWebhook, $request);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
($eventType === "start" && $twitchWebhook->webhookActions) ||
|
($eventType === "start" && $twitchWebhook->discordWebhookMessage) ||
|
||||||
(config("app.env") !== "production" && $twitchWebhook->webhookActions && $eventType !== "end")
|
(config("app.env") !== "production" && $twitchWebhook->discordWebhookMessage && $eventType !== "end")
|
||||||
) {
|
) {
|
||||||
|
$data = $request->all()["data"][0];
|
||||||
|
|
||||||
// TODO: move this (generation of the message embed) to a separate method
|
// TODO: move this (generation of the message embed) to a separate method
|
||||||
$userInfo = Twitch::getUserById(intval($data["user_id"]))->data[0];
|
$userInfo = Twitch::getUserById(intval($data["user_id"]))->data[0];
|
||||||
$gameInfo = null;
|
$gameInfo = null;
|
||||||
@@ -215,24 +238,8 @@ class WebhookController extends Controller
|
|||||||
$gameInfo = Twitch::getGameById(intval($data["game_id"]))->data[0];
|
$gameInfo = Twitch::getGameById(intval($data["game_id"]))->data[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
$embed = new DiscordEmbedMessage();
|
foreach($twitchWebhook->discordWebhookMessage as $msg) {
|
||||||
$embed->setUsername(config("app.name"))
|
$this->sendDiscordWebhookMessage($msg, $data, $userInfo, $gameInfo);
|
||||||
->setAvatar(asset("img/twitch-icon.png"))
|
|
||||||
->setContent("¡Hola @everyone! " . $data["user_name"] . " está ahora en directo https://twitch.tv/" . $userInfo->login . " ! Ven a verle :wink:!")
|
|
||||||
// embed
|
|
||||||
->setColor(hexdec(substr("#9147ff", 1)))
|
|
||||||
->setAuthorName($userInfo->display_name)
|
|
||||||
->setAuthorIcon($userInfo->profile_image_url)
|
|
||||||
->setThumbnail($userInfo->profile_image_url)
|
|
||||||
->setImage(str_replace(["{width}", "{height}"], [1280, 720], $data["thumbnail_url"]) . "?cache=" . Carbon::now()->timestamp)
|
|
||||||
->setTitle($data["title"])
|
|
||||||
->setUrl("https://twitch.tv/$userInfo->login")
|
|
||||||
->addField("Juego", $gameInfo ? $gameInfo->name : "─", true)
|
|
||||||
->addField("Espectadores", $data["viewer_count"], true);
|
|
||||||
|
|
||||||
foreach($twitchWebhook->webhookActions as $hookAction) {
|
|
||||||
$webhook = new DiscordWebhook($hookAction->discord_hook_url);
|
|
||||||
$webhook->send($embed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -240,6 +247,52 @@ class WebhookController extends Controller
|
|||||||
return response("Ok", 200);
|
return response("Ok", 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a message to using the DiscordWebhookMessage preferences and the given data
|
||||||
|
*/
|
||||||
|
private function sendDiscordWebhookMessage(DiscordWebhookMessage $msg, $data, $userInfo, $gameInfo) {
|
||||||
|
$hasEmbed = $msg->has_embed;
|
||||||
|
if ($hasEmbed) {
|
||||||
|
$discordMessage = new DiscordEmbedMessage();
|
||||||
|
} else {
|
||||||
|
$discordMessage = new DiscordTextMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
$discordMessage->setUsername($msg->username ?? config("app.name"))
|
||||||
|
->setAvatar($msg->avatar ? Storage::disk("public")->url($msg->avatar) . "?cache=" . Carbon::now()->timestamp : asset("img/twitch-icon.png"))
|
||||||
|
->setContent($msg->content ?? "¡Hola @everyone! " . $userInfo->display_name . " está ahora en directo https://twitch.tv/" . $userInfo->login . " ! Ven a verle :wink:!");
|
||||||
|
|
||||||
|
if ($hasEmbed) {
|
||||||
|
$discordMessage->setColor(hexdec(substr($msg->embed_color ?? "#9147ff", 1)))
|
||||||
|
->setAuthorName($msg->embed_author_name ? $userInfo->display_name : "")
|
||||||
|
->setAuthorIcon($msg->embed_author_icon ? $userInfo->profile_image_url : "")
|
||||||
|
->setAuthorUrl($msg->embed_author_url ? "https://twitch.tv/$userInfo->login" : "")
|
||||||
|
->setThumbnail($msg->embed_thumbnail ? $userInfo->profile_image_url : "")
|
||||||
|
->setImage($msg->embed_image ? str_replace(["{width}", "{height}"], [1280, 720], $data["thumbnail_url"]) . "?cache=" . Carbon::now()->timestamp : "")
|
||||||
|
->setTitle($msg->embed_title ?? $data["title"])
|
||||||
|
->setDescription($msg->embed_description ?? "")
|
||||||
|
->setUrl($msg->embed_url ?? "https://twitch.tv/$userInfo->login");
|
||||||
|
|
||||||
|
foreach(json_decode($msg->embed_fields) as $field) {
|
||||||
|
$value;
|
||||||
|
switch($field->value) {
|
||||||
|
case "%game%":
|
||||||
|
$value = $gameInfo ? $gameInfo->name : "─";
|
||||||
|
break;
|
||||||
|
case "%viewer_count%":
|
||||||
|
$value = $data["viewer_count"];
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$value = $field->value;
|
||||||
|
}
|
||||||
|
$discordMessage->addField($field->name, $value, $field->inline);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$webhook = new DiscordWebhook($msg->discord_webhook_url);
|
||||||
|
$webhook->send($discordMessage);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: implement the method
|
// TODO: implement the method
|
||||||
public function subscription(Request $request) {
|
public function subscription(Request $request) {
|
||||||
if (!$this->verifySecret()) {
|
if (!$this->verifySecret()) {
|
||||||
|
50
app/Http/Requests/CreateDiscordWebhookMessage.php
Normal file
50
app/Http/Requests/CreateDiscordWebhookMessage.php
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class CreateDiscordWebhookMessage extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize()
|
||||||
|
{
|
||||||
|
return Auth::check();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function rules()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
"enabled" => "required|boolean",
|
||||||
|
"discord_webhook_url" => "required|url|starts_with:https://discordapp.com/api/webhooks/,https://discord.com/api/webhooks/",
|
||||||
|
"username" => "nullable|string|max:50",
|
||||||
|
"avatar" => "nullable|file|image",
|
||||||
|
"delete_avatar" => "nullable|in:true,1",
|
||||||
|
"content" => "nullable|string|max:2000",
|
||||||
|
"embed_color" => [
|
||||||
|
"nullable",
|
||||||
|
"string",
|
||||||
|
"regex:/^#[0-9A-F]{6}$/i"
|
||||||
|
],
|
||||||
|
"embed_author_name" => "nullable|boolean",
|
||||||
|
"embed_author_url" => "nullable|boolean",
|
||||||
|
"embed_author_icon" => "nullable|boolean",
|
||||||
|
"embed_thumbnail" => "nullable|boolean",
|
||||||
|
"embed_title" => "nullable|string|max:191",
|
||||||
|
"embed_description" => "nullable|string|max:2000",
|
||||||
|
"embed_url" => "nullable|url|max:191",
|
||||||
|
"embed_image" => "nullable|boolean",
|
||||||
|
"embed_fields" => "nullable|json"
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@@ -25,7 +25,10 @@ class TestWebhookAction extends FormRequest
|
|||||||
public function rules()
|
public function rules()
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
"discord_hook_url" => "required|url|starts_with:https://discordapp.com/api/webhooks/,https://discord.com/api/webhooks/"
|
"discord_webhook_url" => "required|url|starts_with:https://discordapp.com/api/webhooks/,https://discord.com/api/webhooks/",
|
||||||
|
"username" => "nullable|string|max:50",
|
||||||
|
"avatar" => "nullable|url",
|
||||||
|
"content" => "nullable|string|max:2000"
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -34,4 +34,13 @@ class TwitchWebhook extends Model
|
|||||||
public function webhookActions() {
|
public function webhookActions() {
|
||||||
return $this->hasMany("App\WebhookAction", "webhook_id");
|
return $this->hasMany("App\WebhookAction", "webhook_id");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// event handlers
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get discord message/embed assigned to this twitch webhook
|
||||||
|
*/
|
||||||
|
public function discordWebhookMessage() {
|
||||||
|
return $this->morphedByMany("App\DiscordWebhookMessage", "twitch_webhook_event");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class CreateTwitchWebhookEventsTable extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::create('twitch_webhook_events', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->foreignId("twitch_webhook_id")->references("id")->on("twitch_webhooks")->onUpdate("cascade")->onDelete("cascade");
|
||||||
|
$table->unsignedBigInteger("twitch_webhook_event_id");
|
||||||
|
$table->string("twitch_webhook_event_type");
|
||||||
|
$table->unique(["twitch_webhook_id", "twitch_webhook_event_type"]);
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('twitch_webhook_events');
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class CreateDiscordWebhookMessagesTable extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::create('discord_webhook_messages', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->boolean("enabled")->default(true);
|
||||||
|
$table->string("discord_webhook_url");
|
||||||
|
$table->string("username")->nullable();
|
||||||
|
$table->string("avatar")->nullable();
|
||||||
|
$table->text("content")->nullable();
|
||||||
|
$table->boolean("has_embed")->default(false);
|
||||||
|
$table->string("embed_color", 7)->default("#9147ff");
|
||||||
|
$table->boolean("embed_author_name")->default(false);
|
||||||
|
$table->boolean("embed_author_url")->default(false);
|
||||||
|
$table->boolean("embed_author_icon")->default(false);
|
||||||
|
$table->boolean("embed_thumbnail")->default(false);
|
||||||
|
$table->string("embed_title")->nullable();
|
||||||
|
$table->text("embed_description")->nullable();
|
||||||
|
$table->string("embed_url")->nullable();
|
||||||
|
$table->boolean("embed_image")->default(false);
|
||||||
|
$table->json("embed_fields")->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('discord_webhook_messages');
|
||||||
|
}
|
||||||
|
}
|
87
resources/js/components/auth/StreamEventComponent.vue
Normal file
87
resources/js/components/auth/StreamEventComponent.vue
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
<template>
|
||||||
|
<section>
|
||||||
|
<h3>
|
||||||
|
<label class="uk-flex uk-flex-between uk-flex-middle">
|
||||||
|
<span>
|
||||||
|
Stream (stream start, update and finish)
|
||||||
|
</span>
|
||||||
|
<span class="switch">
|
||||||
|
<input type="checkbox" v-model="enabled" @change="toggleStatus">
|
||||||
|
<span class="slider round"></span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</h3>
|
||||||
|
<transition enter-active-class="animate__animated animate__slideInUp" leave-active-class="animate__animated animate__slideOutDown">
|
||||||
|
<discord-webhook-message
|
||||||
|
v-if="enabled"
|
||||||
|
:data="JSON.parse($props.discordWebhookMessage)"
|
||||||
|
></discord-webhook-message>
|
||||||
|
</transition>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import UIkit from "uikit";
|
||||||
|
import { axios } from "../../app";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
active: {
|
||||||
|
type: Boolean,
|
||||||
|
value: false
|
||||||
|
},
|
||||||
|
subUnsubUrl: String,
|
||||||
|
saveHookUrl: String,
|
||||||
|
sendTestMessageUrl: String,
|
||||||
|
discordWebhookMessage: String
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
enabled: this.active,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async toggleStatus() {
|
||||||
|
let type = "stream";
|
||||||
|
let mode = `${this.enabled ? "" : "un"}subscribe`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
let req = await axios.post(this.subUnsubUrl, {
|
||||||
|
type,
|
||||||
|
mode
|
||||||
|
});
|
||||||
|
|
||||||
|
UIkit.notification({
|
||||||
|
message: `Saved!`,
|
||||||
|
status: "success"
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
this.enabled = !this.enabled;
|
||||||
|
let errorMsg = `Error toggling the ${type} hook, retry after some seconds.`;
|
||||||
|
|
||||||
|
switch(e.request.status) {
|
||||||
|
case 422:
|
||||||
|
errorMsg = e.response.data.message;
|
||||||
|
break;
|
||||||
|
case 503:
|
||||||
|
errorMsg = "Site is in maintenance, retry in some minutes...";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
UIkit.notification({
|
||||||
|
message: errorMsg,
|
||||||
|
status: "danger"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style computed>
|
||||||
|
/* mask the hook url unless editing or empty */
|
||||||
|
input[type="url"]:not(:focus):not(:placeholder-shown) {
|
||||||
|
filter: blur(4px);
|
||||||
|
}
|
||||||
|
</style>
|
@@ -0,0 +1,420 @@
|
|||||||
|
<template>
|
||||||
|
<form action="javascript:" class="uk-form-stacked" novalidate>
|
||||||
|
<div class="uk-margin">
|
||||||
|
<span uk-icon="info" uk-tooltip="Url of the webhook"></span>
|
||||||
|
<label class="uk-form-label" for="dwm-discord_webhook_url">
|
||||||
|
Discord webhook url: *
|
||||||
|
</label>
|
||||||
|
<span class="uk-text-danger" v-if="errors.discord_webhook_url">
|
||||||
|
{{ errors.discord_webhook_url[0] }}
|
||||||
|
</span>
|
||||||
|
<div class="uk-form-controls">
|
||||||
|
<input class="uk-input" name="discord_webhook_url" id="dwm-discord_webhook_url" type="url" placeholder="https://discordapp.com/api/webhooks/..." v-model="discord_webhook_url" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="uk-margin">
|
||||||
|
<label class="uk-form-label" for="dwm-username">
|
||||||
|
<span uk-icon="info" uk-tooltip="Username shown when sending messages"></span>
|
||||||
|
Username:
|
||||||
|
</label>
|
||||||
|
<span class="uk-text-danger" v-if="errors.username">
|
||||||
|
{{ errors.username[0] }}
|
||||||
|
</span>
|
||||||
|
<div class="uk-form-controls">
|
||||||
|
<input class="uk-input" name="username" id="dwm-username" type="text" placeholder="Username of the hook sender" v-model="username" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="uk-margin">
|
||||||
|
<label class="uk-form-label" for="dwm-avatar">
|
||||||
|
<span uk-icon="info" uk-tooltip="Profile image of the user"></span>
|
||||||
|
Username avatar:
|
||||||
|
</label>
|
||||||
|
<span class="uk-text-danger" v-if="errors.avatar">
|
||||||
|
{{ errors.avatar[0] }}
|
||||||
|
</span>
|
||||||
|
<div class="uk-form-controls">
|
||||||
|
<input name="avatar" id="dwm-avatar" type="file" class="uk-margin-small-right" @change="updateAvatar" />
|
||||||
|
<template v-if="avatarPreview">
|
||||||
|
<img :src="avatarPreview" alt="Bot avatar image" class="uk-margin-smalls-right" id="dwm-avatar-preview" />
|
||||||
|
<br>
|
||||||
|
<p v-if="avatarPreview.startsWith('data:image')"><strong>Note:</strong> before sending a test message you have to upload the new avatar image.</p>
|
||||||
|
<span v-if="!avatarPreview.startsWith('data:image')" class="uk-button uk-button-link" @click="deleteAvatar">Delete avatar</span>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="uk-margin">
|
||||||
|
<label class="uk-form-label" for="dwm-content">
|
||||||
|
<span uk-icon="info" uk-tooltip="Content of the text message"></span>
|
||||||
|
Content:
|
||||||
|
</label>
|
||||||
|
<span class="uk-text-danger" v-if="errors.content">
|
||||||
|
{{ errors.content[0] }}
|
||||||
|
</span>
|
||||||
|
<div class="uk-form-controls">
|
||||||
|
<textarea class="uk-textarea" name="content" id="dwm-content" rows="3" placeholder="Text message, you can ping @everyone from here" v-model="content"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h4>
|
||||||
|
<label class="uk-flex uk-flex-between uk-flex-middle">
|
||||||
|
<span>
|
||||||
|
Embed options
|
||||||
|
</span>
|
||||||
|
<span class="switch">
|
||||||
|
<input type="checkbox" v-model="has_embed">
|
||||||
|
<span class="slider round"></span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</h4>
|
||||||
|
<transition enter-active-class="animate__animated animate__slideInUp" leave-active-class="animate__animated animate__slideOutDown">
|
||||||
|
<div v-if="has_embed">
|
||||||
|
<p>
|
||||||
|
<strong>Note:</strong> test messages will not have any of these fields.
|
||||||
|
</p>
|
||||||
|
<div class="uk-margin">
|
||||||
|
<label class="uk-form-label" for="dwm-embed_color">
|
||||||
|
<span uk-icon="info" uk-tooltip="Color shown at the left of the embed"></span>
|
||||||
|
Color:
|
||||||
|
</label>
|
||||||
|
<span class="uk-text-danger" v-if="errors.embed_color">
|
||||||
|
{{ errors.embed_color[0] }}
|
||||||
|
</span>
|
||||||
|
<div class="uk-form-controls">
|
||||||
|
<input class="uk-input" name="embed_color" id="dwm-embed_color" type="color" v-model="embed_color" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="uk-margin uk-margin-small-right uk-display-inline-block">
|
||||||
|
<label class="uk-form-label" for="dwm-embed_author_name">
|
||||||
|
<span uk-icon="info" uk-tooltip="Show your Twitch username as the author name"></span>
|
||||||
|
Author name:
|
||||||
|
<span class="switch uk-display-inline-block">
|
||||||
|
<input class="uk-checkbox" name="embed_author_name" id="dwm-embed_author_name" type="checkbox" v-model="embed_author_name" />
|
||||||
|
<span class="slider round"></span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="uk-margin uk-margin-small-right uk-display-inline-block">
|
||||||
|
<label class="uk-form-label" for="dwm-embed_author_url">
|
||||||
|
<span uk-icon="info" uk-tooltip="Link to the Twitch channel url"></span>
|
||||||
|
Author url:
|
||||||
|
<span class="switch">
|
||||||
|
<input class="uk-checkbox" name="embed_author_url" id="dwm-embed_author_url" type="checkbox" v-model="embed_author_url" />
|
||||||
|
<span class="slider round"></span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="uk-margin uk-margin-small-right uk-display-inline-block">
|
||||||
|
<label class="uk-form-label" for="dwm-embed_author_icon">
|
||||||
|
<span uk-icon="info" uk-tooltip="Show Twitch profile picture as author icon"></span>
|
||||||
|
Author icon:
|
||||||
|
<span class="switch">
|
||||||
|
<input class="uk-checkbox" name="embed_author_icon" id="dwm-embed_author_icon" type="checkbox" v-model="embed_author_icon" />
|
||||||
|
<span class="slider round"></span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="uk-margin uk-display-inline-block">
|
||||||
|
<label class="uk-form-label" for="dwm-embed_thumbnail">
|
||||||
|
<span uk-icon="info" uk-tooltip="Show Twitch profile picture on the top right"></span>
|
||||||
|
Thumbnail:
|
||||||
|
<span class="switch">
|
||||||
|
<input class="uk-checkbox" name="embed_thumbnail" id="dwm-embed_thumbnail" type="checkbox" v-model="embed_thumbnail" />
|
||||||
|
<span class="slider round"></span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="uk-margin">
|
||||||
|
<label class="uk-form-label" for="dwm-embed_title">
|
||||||
|
<span uk-icon="info" uk-tooltip="Keep empty to use stream title"></span>
|
||||||
|
Title:
|
||||||
|
</label>
|
||||||
|
<span class="uk-text-danger" v-if="errors.embed_title">
|
||||||
|
{{ errors.embed_title[0] }}
|
||||||
|
</span>
|
||||||
|
<div class="uk-form-controls">
|
||||||
|
<input class="uk-input" name="embed_title" id="dwm-embed_title" type="text" placeholder="Title of the embed, keep empty to use stream title" v-model="embed_title" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="uk-margin">
|
||||||
|
<label class="uk-form-label" for="dwm-embed_description">
|
||||||
|
<span uk-icon="info" uk-tooltip="Description of the embed"></span>
|
||||||
|
Description:
|
||||||
|
</label>
|
||||||
|
<span class="uk-text-danger" v-if="errors.embed_description">
|
||||||
|
{{ errors.embed_description[0] }}
|
||||||
|
</span>
|
||||||
|
<div class="uk-form-controls">
|
||||||
|
<textarea class="uk-textarea" name="embed_description" id="dwm-embed_description" rows="3" placeholder="Description of the embed" v-model="embed_description"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="uk-margin">
|
||||||
|
<label class="uk-form-label" for="dwm-embed_url">
|
||||||
|
<span uk-icon="info" uk-tooltip="Keep empty to use stream url"></span>
|
||||||
|
Url:
|
||||||
|
</label>
|
||||||
|
<span class="uk-text-danger" v-if="errors.embed_url">
|
||||||
|
{{ errors.embed_url[0] }}
|
||||||
|
</span>
|
||||||
|
<div class="uk-form-controls">
|
||||||
|
<input class="uk-input" name="embed_url" id="dwm-embed_url" type="url" placeholder="Url to redirec to when clicking the title, keep empty to use stream url" v-model="embed_url" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="uk-margin">
|
||||||
|
<label class="uk-form-label" for="dwm-embed_image">
|
||||||
|
<span uk-icon="info" uk-tooltip="Show stream preview image"></span>
|
||||||
|
Image:
|
||||||
|
</label>
|
||||||
|
<label class="uk-form-controls">
|
||||||
|
<span class="switch">
|
||||||
|
<input class="uk-checkbox" name="embed_image" id="dwm-embed_image" type="checkbox" v-model="embed_image" />
|
||||||
|
<span class="slider round"></span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="uk-margin">
|
||||||
|
<label class="uk-form-label">
|
||||||
|
<span uk-icon="info" uk-tooltip="Custom fields"></span>
|
||||||
|
Fields:
|
||||||
|
</label>
|
||||||
|
<span class="uk-text-danger" v-if="errors.embed_fields">
|
||||||
|
{{ errors.embed_fields[0] }}
|
||||||
|
</span>
|
||||||
|
<div class="uk-form-controls">
|
||||||
|
<div>
|
||||||
|
Check the toggle to show the field in the same line as other fields, else each field will be shown on a different line.
|
||||||
|
<br/>
|
||||||
|
Available variables, set the value field to one of these to replace it with data taken from the stream.
|
||||||
|
<ul>
|
||||||
|
<li><code>%game%</code> name of the game</li>
|
||||||
|
<li><code>%viewer_count%</code> number of viewers</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div v-for="(field, i) in embed_fields" :key="i" :class="{'uk-margin-small-bottom': (i == (embed_fields.length - 1)) }" class="uk-child-width-auto uk-flex uk-flex-middle">
|
||||||
|
<input class="uk-input flex-grow" name="field[]" type="text" placeholder="Field name" v-model="field.name" />
|
||||||
|
<input class="uk-input flex-grow" name="fieldValue[]" type="text" placeholder="Field value" v-model="field.value" />
|
||||||
|
<label class="switch uk-margin-small-left uk-margin-small-right">
|
||||||
|
<input class="uk-checkbox" name="fieldInline[]" type="checkbox" v-model="field.inline" />
|
||||||
|
<span class="slider round"></span>
|
||||||
|
</label>
|
||||||
|
<span uk-icon="close" @click="removeField(i)"></span>
|
||||||
|
</div>
|
||||||
|
<button class="uk-button uk-button-small uk-button-default" @click="addField">
|
||||||
|
<span uk-icon="plus"></span> Add field
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
<div class="uk-margin">
|
||||||
|
<button class="uk-button uk-button-primary" @click="save">
|
||||||
|
<span uk-icon="check"></span>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
<button class="uk-button uk-button-default" @click="sendTestMessage" v-if="avatarPreview && !avatarPreview.startsWith('data:image')">
|
||||||
|
<span uk-icon="forward"></span>
|
||||||
|
Send test message
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</template>
|
||||||
|
<style computed>
|
||||||
|
#dwm-avatar-preview {
|
||||||
|
width: 150px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
import UIkit from "uikit";
|
||||||
|
import { axios } from "../../../app";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
data: {
|
||||||
|
type: Object
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
id: null,
|
||||||
|
discord_webhook_url: this.data.discord_webhook_url,
|
||||||
|
username: this.data.username,
|
||||||
|
avatar: null,
|
||||||
|
avatarPreview: this.data.avatar ? `${location.protocol}//${location.host}/storage/${this.data.avatar}` : "",
|
||||||
|
content: this.data.content,
|
||||||
|
has_embed: parseInt(this.data.has_embed),
|
||||||
|
embed_color: this.data.embed_color ? this.data.embed_color : "#9147ff",
|
||||||
|
embed_author_name: parseInt(this.data.embed_author_name),
|
||||||
|
embed_author_url: parseInt(this.data.embed_author_url),
|
||||||
|
embed_author_icon: parseInt(this.data.embed_author_icon),
|
||||||
|
embed_thumbnail: parseInt(this.data.embed_thumbnail),
|
||||||
|
embed_title: this.data.embed_title,
|
||||||
|
embed_description: this.data.embed_description,
|
||||||
|
embed_url: this.data.embed_url,
|
||||||
|
embed_image: parseInt(this.data.embed_image),
|
||||||
|
embed_fields: this.data.embed_fields ? JSON.parse(this.data.embed_fields) : [],
|
||||||
|
errors: {}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateAvatar(e) {
|
||||||
|
let file = e.target.files[0];
|
||||||
|
|
||||||
|
const fr = new FileReader();
|
||||||
|
fr.addEventListener("load", () => {
|
||||||
|
this.avatarPreview = fr.result;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
fr.readAsDataURL(file);
|
||||||
|
this.avatar = file;
|
||||||
|
} else {
|
||||||
|
this.avatar = null;
|
||||||
|
this.avatarPreview = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
addField() {
|
||||||
|
this.embed_fields.push({name: "", value: "", inline: false});
|
||||||
|
},
|
||||||
|
removeField(idx) {
|
||||||
|
this.embed_fields.splice(idx, 1);
|
||||||
|
},
|
||||||
|
async save() {
|
||||||
|
try {
|
||||||
|
const fd = new FormData();
|
||||||
|
fd.set("type", "stream");
|
||||||
|
fd.set("enabled", true ? 1 : 0);
|
||||||
|
fd.set("discord_webhook_url", this.discord_webhook_url);
|
||||||
|
fd.set("username", this.username ? this.username : '');
|
||||||
|
if (this.avatar) {
|
||||||
|
fd.set("avatar", this.avatar);
|
||||||
|
}
|
||||||
|
fd.set("content", this.content ? this.content : '');
|
||||||
|
fd.set("has_embed", this.has_embed ? 1 : 0);
|
||||||
|
fd.set("embed_color", this.embed_color);
|
||||||
|
fd.set("embed_author_name", this.embed_author_name ? 1 : 0);
|
||||||
|
fd.set("embed_author_url", this.embed_author_url ? 1 : 0);
|
||||||
|
fd.set("embed_author_icon", this.embed_author_icon ? 1 : 0);
|
||||||
|
fd.set("embed_thumbnail", this.embed_thumbnail ? 1 : 0);
|
||||||
|
fd.set("embed_title", this.embed_title ? this.embed_title : '');
|
||||||
|
fd.set("embed_description", this.embed_description ? this.embed_description : '');
|
||||||
|
fd.set("embed_url", this.embed_url ? this.embed_url : '');
|
||||||
|
fd.set("embed_image", this.embed_image ? 1 : 0);
|
||||||
|
fd.set("embed_fields", this.embed_fields ? JSON.stringify(this.embed_fields.filter(e => e.name && e.value)) : []);
|
||||||
|
|
||||||
|
let req = await axios.post(this.$parent.saveHookUrl, fd, {
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "multipart/form-data"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (req.data.action_id) {
|
||||||
|
this.id = req.data.action_id;
|
||||||
|
}
|
||||||
|
if (req.data.avatar) {
|
||||||
|
this.avatar = null;
|
||||||
|
this.avatarPreview = `${req.data.avatar}?${Date.now()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
UIkit.notification({
|
||||||
|
message: "Saved!",
|
||||||
|
status: "success"
|
||||||
|
});
|
||||||
|
|
||||||
|
this.errors = {};
|
||||||
|
|
||||||
|
} catch(e) {
|
||||||
|
let errorMsg = e.message;
|
||||||
|
switch(e.request.status) {
|
||||||
|
case 422:
|
||||||
|
errorMsg = e.response.data.message;
|
||||||
|
this.errors = e.response.data.errors;
|
||||||
|
break;
|
||||||
|
case 503:
|
||||||
|
errorMsg = "Site is in maintenance, retry in some minutes...";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
UIkit.notification({
|
||||||
|
message: errorMsg,
|
||||||
|
status: "danger"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async deleteAvatar() {
|
||||||
|
try {
|
||||||
|
let req = await axios.post(this.$parent.saveHookUrl, {
|
||||||
|
type: "stream",
|
||||||
|
enabled: true ? 1 : 0,
|
||||||
|
discord_webhook_url: this.discord_webhook_url,
|
||||||
|
delete_avatar: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
this.avatarPreview = req.data.avatar;
|
||||||
|
|
||||||
|
UIkit.notification({
|
||||||
|
message: "Avatar reseted to default",
|
||||||
|
status: "success"
|
||||||
|
});
|
||||||
|
|
||||||
|
this.errors = {};
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
let errorMsg = e.message;
|
||||||
|
|
||||||
|
switch(e.request.status) {
|
||||||
|
case 422:
|
||||||
|
errorMsg = e.response.data.message;
|
||||||
|
this.errors = e.response.data.errors;
|
||||||
|
break;
|
||||||
|
case 503:
|
||||||
|
errorMsg = "Site is in maintenance, retry in some minutes...";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
UIkit.notification({
|
||||||
|
message: errorMsg,
|
||||||
|
status: "danger"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async sendTestMessage() {
|
||||||
|
try {
|
||||||
|
let req = await axios.post(this.$parent.sendTestMessageUrl, {
|
||||||
|
discord_webhook_url: this.discord_webhook_url,
|
||||||
|
username: this.username,
|
||||||
|
avatar: this.avatarPreview,
|
||||||
|
content: this.content
|
||||||
|
});
|
||||||
|
|
||||||
|
UIkit.notification({
|
||||||
|
message: "Test message sent!",
|
||||||
|
status: "success"
|
||||||
|
});
|
||||||
|
|
||||||
|
this.errors = {};
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
let errorMsg = e.message;
|
||||||
|
switch(e.request.status) {
|
||||||
|
case 422:
|
||||||
|
errorMsg = e.response.data.message;
|
||||||
|
this.errors = e.response.data.errors;
|
||||||
|
break;
|
||||||
|
case 503:
|
||||||
|
errorMsg = "Site is in maintenance, retry in some minutes...";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
UIkit.notification({
|
||||||
|
message: errorMsg,
|
||||||
|
status: "danger"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
@@ -13,5 +13,4 @@
|
|||||||
@endif
|
@endif
|
||||||
|
|
||||||
<h3 class="uk-text-center">Site under construction</h3>
|
<h3 class="uk-text-center">Site under construction</h3>
|
||||||
|
|
||||||
@endsection
|
@endsection
|
||||||
|
@@ -9,197 +9,14 @@
|
|||||||
|
|
||||||
<h2>Available events</h2>
|
<h2>Available events</h2>
|
||||||
|
|
||||||
<section>
|
<stream-event
|
||||||
<h3>
|
sub-unsub-url="{{ route("hooks.twitch.subscribe") }}"
|
||||||
<label class="uk-flex uk-flex-between">
|
save-hook-url="{{ route("hooks.twitch.action.add") }}"
|
||||||
<span>
|
send-test-message-url="{{ route("hooks.twitch.action.test.discord") }}"
|
||||||
Stream (stream start, update and finish)
|
v-bind:active="{{ ($streamHook && !($streamHook->disabled || $streamHook->disabled_at)) ? 'true' : 'false' }}"
|
||||||
</span>
|
|
||||||
<span class="switch">
|
|
||||||
<input type="checkbox" id="stream-hook" {{ ($streamHook && !($streamHook->disabled || $streamHook->disabled_at)) ? "checked" : "" }}>
|
|
||||||
<span class="slider round"></span>
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</h3>
|
|
||||||
<div>
|
|
||||||
<p>
|
|
||||||
<span uk-icon="info"></span> A message will be sent to the channel when a stream is started.
|
|
||||||
</p>
|
|
||||||
<div role="form" class="uk-flex" id="discord-webhook">
|
|
||||||
<input type="url" class="uk-input" name="discord_hook_url" placeholder="Discord webhook URL" value="{{ ($streamHook && $streamHook->webhookActions()->first()) ? $streamHook->webhookActions()->first()->discord_hook_url : "" }}" {{ (!$streamHook || ($streamHook->disabled || $streamHook->disabled_at)) ? "disabled" : "" }} />
|
|
||||||
<button name="save" class="uk-button uk-button-primary uk-hidden" uk-icon="check" style="flex-shrink: 0;"></button>
|
|
||||||
<button name="test" class="uk-button uk-button-default" style="flex-shrink: 0;">Send test message</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<style>
|
discord-webhook-message="{{ ($streamHook && $streamHook->discordWebhookMessage()->first()) ? $streamHook->discordWebhookMessage()->first() : "{}" }}"
|
||||||
/* mask the hook url unless editing or empty */
|
></stream-event>
|
||||||
#discord-webhook input[type="url"]:not(:focus):not(:placeholder-shown) {
|
|
||||||
filter: blur(4px);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
let streamHook = document.getElementById("stream-hook");
|
|
||||||
|
|
||||||
let hookActionForm = document.getElementById("discord-webhook");
|
|
||||||
let discordWebhookUrl = hookActionForm.querySelector("[name=discord_hook_url]");
|
|
||||||
let discordWebhookSaveBtn = hookActionForm.querySelector("[name=save]");
|
|
||||||
let discordWebhookTestBtn = hookActionForm.querySelector("[name=test]");
|
|
||||||
|
|
||||||
let INIT_DISCORD_HOOK_URL = discordWebhookUrl.value;
|
|
||||||
|
|
||||||
// subscribe unsubscribe from stream hook events
|
|
||||||
streamHook.addEventListener("change", async function() {
|
|
||||||
let type = "stream";
|
|
||||||
let mode = `${this.checked ? "" : "un"}subscribe`;
|
|
||||||
|
|
||||||
discordWebhookUrl.disabled = !discordWebhookUrl.disabled;
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
let req = await axios.post("{{ route("hooks.twitch.subscribe") }}", {
|
|
||||||
type: type,
|
|
||||||
mode: mode
|
|
||||||
});
|
|
||||||
|
|
||||||
UIkit.notification({
|
|
||||||
message: `Saved!`,
|
|
||||||
status: "success"
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
|
|
||||||
this.checked = !this.checked;
|
|
||||||
discordWebhookUrl.disabled = !discordWebhookUrl.disabled;
|
|
||||||
|
|
||||||
let errorMsg = `Error toggling the ${type} hook, retry after some seconds.`;
|
|
||||||
|
|
||||||
// validation error
|
|
||||||
switch(e.request.status) {
|
|
||||||
case 422:
|
|
||||||
errorMsg = e.response.data.message;
|
|
||||||
break;
|
|
||||||
case 503:
|
|
||||||
errorMsg = "Site is in maintenance, retry in some minutes...";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.log(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
UIkit.notification({
|
|
||||||
message: errorMsg,
|
|
||||||
status: "danger"
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// toggle the visibility of the save button
|
|
||||||
discordWebhookUrl.addEventListener("keyup", function () {
|
|
||||||
let show = false;
|
|
||||||
|
|
||||||
if (this.value.trim().length >= "https://discordapp.com/api/webhooks/".length) {
|
|
||||||
|
|
||||||
if (this.value !== INIT_DISCORD_HOOK_URL) {
|
|
||||||
show = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
(show && discordWebhookSaveBtn.classList.contains("uk-hidden"))
|
|
||||||
||
|
|
||||||
(!show && !discordWebhookSaveBtn.classList.contains("uk-hidden"))
|
|
||||||
) {
|
|
||||||
discordWebhookSaveBtn.classList.toggle("uk-hidden");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// save discord webhook url
|
|
||||||
discordWebhookSaveBtn.addEventListener("click", async function () {
|
|
||||||
// disable editing the url and hide save button
|
|
||||||
discordWebhookUrl.toggleAttribute("disabled");
|
|
||||||
this.classList.toggle("uk-hidden");
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
let req = await axios.post("{{ route("hooks.twitch.action.add") }}", {
|
|
||||||
type: "stream",
|
|
||||||
discord_hook_url: discordWebhookUrl.value
|
|
||||||
});
|
|
||||||
|
|
||||||
UIkit.notification({
|
|
||||||
message: `Updated!`,
|
|
||||||
status: "success"
|
|
||||||
});
|
|
||||||
|
|
||||||
INIT_DISCORD_HOOK_URL = discordWebhookUrl.value;
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
let errorMsg = "The new url could not be saved, try again after some seconds.";
|
|
||||||
|
|
||||||
// validation error
|
|
||||||
switch(e.request.status) {
|
|
||||||
case 422:
|
|
||||||
errorMsg = e.response.data.errors[discordWebhookUrl.name];
|
|
||||||
break;
|
|
||||||
case 503:
|
|
||||||
errorMsg = "Site is in maintenance, retry in some minutes...";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.log(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
UIkit.notification({
|
|
||||||
message: errorMsg,
|
|
||||||
status: "danger"
|
|
||||||
});
|
|
||||||
|
|
||||||
this.classList.toggle("uk-hidden");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// make editable the webhook url
|
|
||||||
discordWebhookUrl.toggleAttribute("disabled");
|
|
||||||
});
|
|
||||||
|
|
||||||
// send test message
|
|
||||||
discordWebhookTestBtn.addEventListener("click", async function () {
|
|
||||||
try {
|
|
||||||
let req = await axios.post("{{ route("hooks.twitch.action.test.discord") }}", {
|
|
||||||
discord_hook_url: discordWebhookUrl.value
|
|
||||||
});
|
|
||||||
|
|
||||||
UIkit.notification({
|
|
||||||
message: `Test message sent`,
|
|
||||||
status: "success"
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
|
|
||||||
let errorMsg = "There was an error sending the test message.";
|
|
||||||
|
|
||||||
// validation error
|
|
||||||
switch(e.request.status) {
|
|
||||||
case 422:
|
|
||||||
errorMsg = e.response.data.errors[discordWebhookUrl.name];
|
|
||||||
break;
|
|
||||||
case 503:
|
|
||||||
errorMsg = "Site is in maintenance, retry in some minutes...";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.log(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
UIkit.notification({
|
|
||||||
message: errorMsg,
|
|
||||||
status: "danger"
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user