First commit
This commit is contained in:
80
app/Console/Kernel.php
Normal file
80
app/Console/Kernel.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console;
|
||||
|
||||
use App\TwitchWebhook;
|
||||
use Carbon\Carbon;
|
||||
use App\Http\Controllers\WebhookController;
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||
use romanzipp\Twitch\Facades\Twitch;
|
||||
|
||||
class Kernel extends ConsoleKernel
|
||||
{
|
||||
/**
|
||||
* The Artisan commands provided by your application.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $commands = [
|
||||
//
|
||||
];
|
||||
|
||||
/**
|
||||
* Define the application's command schedule.
|
||||
*
|
||||
* @param \Illuminate\Console\Scheduling\Schedule $schedule
|
||||
* @return void
|
||||
*/
|
||||
protected function schedule(Schedule $schedule)
|
||||
{
|
||||
// resubscribe to nearly expired hooks (1 hour after expire)
|
||||
$schedule->call(function () {
|
||||
$hooks = TwitchWebhook::where("disabled", false)->where("disabled_at", null)->whereDate("expires_at", "<=", Carbon::now()->addHours())->get();
|
||||
|
||||
foreach ($hooks as $hook) {
|
||||
$subscription = Twitch::subscribeWebhook($hook->callback, $hook->type, WebhookController::MAX_LEASE_SECONDS);
|
||||
|
||||
if ($subscription->success) {
|
||||
$hook->expires_at = Carbon::now()->addSeconds(WebhookController::MAX_LEASE_SECONDS);
|
||||
$hook->save();
|
||||
|
||||
} else {
|
||||
// TODO: handle error
|
||||
}
|
||||
}
|
||||
|
||||
})->everyThirtyMinutes();
|
||||
|
||||
// unsubscribe from disabled hooks (disabled 1 day ago)
|
||||
$schedule->call(function () {
|
||||
$hooks = TwitchWebhook::where("disabled", false)->whereDate("disabled_at", "<=", Carbon::now()->subDay())->get();
|
||||
|
||||
foreach ($hooks as $hook) {
|
||||
$unsubscription = Twitch::unsubscribeWebhook($hook->callback, $hook->topic);
|
||||
|
||||
if ($unsubscription->success) {
|
||||
$hook->disabled = true;
|
||||
$hook->save();
|
||||
|
||||
} else {
|
||||
// TODO: handle error
|
||||
}
|
||||
|
||||
}
|
||||
})->everyThirtyMinutes();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the commands for the application.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function commands()
|
||||
{
|
||||
$this->load(__DIR__.'/Commands');
|
||||
|
||||
require base_path('routes/console.php');
|
||||
}
|
||||
}
|
55
app/Exceptions/Handler.php
Normal file
55
app/Exceptions/Handler.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||
use Throwable;
|
||||
|
||||
class Handler extends ExceptionHandler
|
||||
{
|
||||
/**
|
||||
* A list of the exception types that are not reported.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $dontReport = [
|
||||
//
|
||||
];
|
||||
|
||||
/**
|
||||
* A list of the inputs that are never flashed for validation exceptions.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $dontFlash = [
|
||||
'password',
|
||||
'password_confirmation',
|
||||
];
|
||||
|
||||
/**
|
||||
* Report or log an exception.
|
||||
*
|
||||
* @param \Throwable $exception
|
||||
* @return void
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function report(Throwable $exception)
|
||||
{
|
||||
parent::report($exception);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render an exception into an HTTP response.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Throwable $exception
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function render($request, Throwable $exception)
|
||||
{
|
||||
return parent::render($request, $exception);
|
||||
}
|
||||
}
|
13
app/Http/Controllers/Controller.php
Normal file
13
app/Http/Controllers/Controller.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||
use Illuminate\Routing\Controller as BaseController;
|
||||
|
||||
class Controller extends BaseController
|
||||
{
|
||||
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
|
||||
}
|
169
app/Http/Controllers/UserController.php
Normal file
169
app/Http/Controllers/UserController.php
Normal file
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\User;
|
||||
use App\UserAccessToken;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use romanzipp\Twitch\Facades\Twitch;
|
||||
use romanzipp\Twitch\Enums\Scope;
|
||||
|
||||
class UserController extends Controller
|
||||
{
|
||||
const OAUTH_BASE_URI = "https://id.twitch.tv/oauth2/";
|
||||
const AUTH_REDIRECT_ROUTE = "me.dashboard";
|
||||
|
||||
public function __construct() {
|
||||
$this->middleware("guest")->only(["login", "authorized"]);
|
||||
|
||||
$this->middleware("auth")->except(["login", "authorized"]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect to the Twitch OAuth app authorization site.
|
||||
*/
|
||||
public function login() {
|
||||
$stateToken = getToken(25);
|
||||
session()->flash("stateToken", $stateToken);
|
||||
|
||||
return redirect()->away(Twitch::getOAuthAuthorizeUrl("code", [
|
||||
Scope::USER_READ_EMAIL
|
||||
], $stateToken));
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback of the Twitch OAuth authorization page.
|
||||
*
|
||||
* @param Illuminate\Http\Request $request
|
||||
*/
|
||||
public function authorized(Request $request) {
|
||||
if ($request->error) {
|
||||
session()->flash("error", [
|
||||
"title" => "Authorization error",
|
||||
"description" => $request->error_description . "."
|
||||
]);
|
||||
|
||||
return redirect(route("home"));
|
||||
|
||||
} else if (session("stateToken") && session("stateToken") != $request->state) {
|
||||
session()->flash("error", [
|
||||
"title" => "Missmatch state token",
|
||||
"description" => "State tokens do not match, maybe someone tried to intercept the login attempt. Try again in sone seconds, if it persists contact with the site administrator."
|
||||
]);
|
||||
return redirect(route("home"));
|
||||
}
|
||||
|
||||
$oauthTokenRequest = Twitch::getOAuthToken($request->code);
|
||||
|
||||
if (!$oauthTokenRequest->success) {
|
||||
return response("Error " . $oauthTokenRequest->data->status . ", " . $oauthTokenRequest->data->message);
|
||||
}
|
||||
|
||||
$userAccessToken = $oauthTokenRequest->data->access_token;
|
||||
$user = $this->getAuthedUser($userAccessToken);
|
||||
|
||||
$dbUser = User::where("twitch_uid", $user->id)->first();
|
||||
|
||||
if (!$dbUser) {
|
||||
$dbUser = $this->store($user);
|
||||
}
|
||||
|
||||
$dbUser->accessTokens()->create([
|
||||
"access_token" => $userAccessToken,
|
||||
"refresh_token" => $oauthTokenRequest->data->refresh_token,
|
||||
"expires_in" => $oauthTokenRequest->data->expires_in
|
||||
]);
|
||||
|
||||
Auth::login($dbUser);
|
||||
session(["oauthToken" => $userAccessToken]);
|
||||
|
||||
return redirect(route(self::AUTH_REDIRECT_ROUTE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get data of a user given a user access token
|
||||
*
|
||||
* @param string $userAccessToken A valid user access token obtained from a OAuth token request.
|
||||
*/
|
||||
private function getAuthedUser($userAccessToken) {
|
||||
$userRequest = Twitch::withToken($userAccessToken)->getAuthedUser();
|
||||
|
||||
if (!$userRequest->success) {
|
||||
session()->flash("error", [
|
||||
"title" => "Could not get user data",
|
||||
"description" => "Something failed while trying to get the user data, is Twitch API down?"
|
||||
]);
|
||||
|
||||
return redirect(route("home"));
|
||||
}
|
||||
|
||||
return $userRequest->data[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a user record on the database given his data
|
||||
*
|
||||
* @param Array $data Array that contains the data of the user (id, display_name, email and profile_image_url).
|
||||
*/
|
||||
private function store($data) {
|
||||
$user = new User;
|
||||
|
||||
$user->twitch_uid = $data->id;
|
||||
$user->username = $data->display_name;
|
||||
$user->email = $data->email;
|
||||
$user->profile_image = $data->profile_image_url;
|
||||
|
||||
$user->save();
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout the user from the session and destroy the user access token created for the session.
|
||||
*
|
||||
* @param Illuminate\Http\Request $request
|
||||
*/
|
||||
public function logout(Request $request) {
|
||||
$logoutRequest = $this->invalidateUserOauthToken(session("oauthToken"));
|
||||
|
||||
if (!$logoutRequest->success) {
|
||||
session()->flash("error", [
|
||||
"title" => "Session could not be ended",
|
||||
"description" => "The user oauth token could not be invalidated, is Twitch API down?"
|
||||
]);
|
||||
return redirect(route("home"));
|
||||
}
|
||||
|
||||
Auth::user()->accessTokens()->where("access_token", session("oauthToken"))->first()->delete();
|
||||
Auth::logout();
|
||||
|
||||
session()->forget("oauthToken");
|
||||
|
||||
return redirect(route("home"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates the given user oauth token.
|
||||
*
|
||||
* @param string $oauthToken user oauth token to be invalidated.
|
||||
*/
|
||||
private function invalidateUserOauthToken($oauthToken) {
|
||||
return Twitch::post(self::OAUTH_BASE_URI . "revoke", [
|
||||
"client_id" => Twitch::getClientId(),
|
||||
"token" => $oauthToken
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the user dashboard.
|
||||
*/
|
||||
public function showDashboard() {
|
||||
return view("me.dashboard", [
|
||||
"followHook" => Auth::user()->twitchWebhooks()->where("type", "follow")->first(),
|
||||
"streamHook" => Auth::user()->twitchWebhooks()->where("type", "stream")->first(),
|
||||
"subscriptionHook" => Auth::user()->twitchWebhooks()->where("type", "subscription")->first(),
|
||||
]);
|
||||
}
|
||||
}
|
258
app/Http/Controllers/WebhookController.php
Normal file
258
app/Http/Controllers/WebhookController.php
Normal file
@@ -0,0 +1,258 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\TwitchWebhook;
|
||||
use App\WebhookAction;
|
||||
use App\Http\Requests\AddWebhookAction;
|
||||
use App\Http\Requests\SubscribeWebhook;
|
||||
use App\Http\Requests\TestWebhookAction;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use romanzipp\Twitch\Facades\Twitch;
|
||||
use Woeler\DiscordPhp\Webhook\DiscordWebhook;
|
||||
use Woeler\DiscordPhp\Message\DiscordEmbedMessage;
|
||||
use Woeler\DiscordPhp\Message\DiscordTextMessage;
|
||||
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
// TODO: document all the methods/constants
|
||||
class WebhookController extends Controller
|
||||
{
|
||||
// 10 days
|
||||
const MAX_LEASE_SECONDS = 864000;
|
||||
const TOPIC_STREAM_CHANGED = "https://api.twitch.tv/helix/streams";
|
||||
|
||||
public function __construct() {
|
||||
$this->middleware("auth")->only([
|
||||
"subscribe",
|
||||
"addAction"
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe or unsubscribe from a hook event
|
||||
*/
|
||||
public function subscribe(SubscribeWebhook $request) {
|
||||
if ($request->mode === "subscribe") {
|
||||
|
||||
switch($request->type) {
|
||||
case "stream":
|
||||
return $this->subscribeStreamHook($request);
|
||||
break;
|
||||
}
|
||||
|
||||
} else if ($request->mode === "unsubscribe") {
|
||||
|
||||
$hook = Auth::user()->twitchWebhooks()->where("type", $request->type)->first();
|
||||
|
||||
if ($hook) {
|
||||
$hook->disabled_at = Carbon::now();
|
||||
$hook->save();
|
||||
}
|
||||
|
||||
return response("Ok");
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: implement in a future more than one action per hook
|
||||
public function addAction(AddWebhookAction $request) {
|
||||
$hook = Auth::user()->twitchWebhooks()->where("type", $request->type)->firstOrFail();
|
||||
|
||||
// TODO: currently hardcoded, allow only one action
|
||||
$actions = $hook->webhookActions();
|
||||
|
||||
// add
|
||||
if (count($actions->get()) == 0) {
|
||||
$action = $actions->create([
|
||||
"discord_hook_url" => $request->discord_hook_url
|
||||
]);
|
||||
|
||||
// edit the existing one
|
||||
} else {
|
||||
$action = $actions->first();
|
||||
$action->discord_hook_url = $request->discord_hook_url;
|
||||
$action->save();
|
||||
}
|
||||
|
||||
return response()->json(["action_id" => $action->id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message to a given discord webhook url
|
||||
*/
|
||||
public function testWebhook(TestWebhookAction $request) {
|
||||
$message = new DiscordTextMessage();
|
||||
$message->setContent("Test message.")
|
||||
->setUsername(config("app.name"))
|
||||
->setAvatar(config("app.url") . "/img/twitch-icon.png");
|
||||
|
||||
$webhook = new DiscordWebhook($request->discord_hook_url);
|
||||
$webhook->send($message);
|
||||
|
||||
return response()->json($webhook);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a webhook to listen for stream changes
|
||||
*/
|
||||
private function subscribeStreamHook(Request $request) {
|
||||
$hookCallback = route("hooks.twitch.stream");
|
||||
// $lease = self::MAX_LEASE_SECONDS;
|
||||
$lease = 3600;
|
||||
$topic = self::TOPIC_STREAM_CHANGED . "?user_id=" . Auth::user()->twitch_uid;
|
||||
|
||||
// TODO: implement secrets, global or specified for each hoock?
|
||||
// $secret = "create a random secret";
|
||||
|
||||
$twitchWebhook = Auth::user()->twitchWebhooks()->firstOrNew([
|
||||
"type" => $request->type
|
||||
], [
|
||||
"topic" => $topic,
|
||||
"callback" => $hookCallback,
|
||||
"expires_at" => Carbon::now()->addSeconds($lease)
|
||||
]);
|
||||
|
||||
$twitchWebhook->disabled = false;
|
||||
$twitchWebhook->disabled_at = null;
|
||||
|
||||
if (!$twitchWebhook->id) {
|
||||
$twitchWebhook->save();
|
||||
$twitchWebhook->callback .= "?hook_id=" . $twitchWebhook->id;
|
||||
$subscription = Twitch::subscribeWebhook($twitchWebhook->callback, $topic, $lease);
|
||||
}
|
||||
|
||||
$twitchWebhook->save();
|
||||
|
||||
if ((isset($subscription) && $subscription->success) || true) {
|
||||
return response("Ok");
|
||||
} else {
|
||||
return response()->json($subscription, $subscription->status);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the webhook (reply to Twitch GET callback after hook creation)
|
||||
*/
|
||||
public function verifySubscription(Request $request) {
|
||||
$twitchHook = TwitchWebhook::findOrFail($request->hook_id);
|
||||
$twitchHook->active = true;
|
||||
$twitchHook->save();
|
||||
|
||||
return response($request->input("hub_challenge"), 200)->header("Content-Type", "text/plain");
|
||||
}
|
||||
|
||||
// TODO: implement the method
|
||||
// listen to follow events
|
||||
public function follow(Request $request) {
|
||||
if (!$this->verifySecret()) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
return "follow";
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream change hook gets here, executes the user configured hook actions
|
||||
*/
|
||||
// TODO: test this, separate the 3 cases: start, update, end
|
||||
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"];
|
||||
Storage::put("stream_hook.json", json_encode($data));
|
||||
|
||||
if ($data) {
|
||||
|
||||
$data = $data[0];
|
||||
|
||||
if (empty($twitchWebhook->live_since)) {
|
||||
$eventType = "start";
|
||||
$twitchWebhook->live_since = Carbon::now();
|
||||
$twitchWebhook->offline_since = null;
|
||||
$twitchWebhook->save();
|
||||
|
||||
} else {
|
||||
$eventType = "update";
|
||||
}
|
||||
|
||||
} else {
|
||||
$eventType = "end";
|
||||
$twitchWebhook->live_since = null;
|
||||
$twitchWebhook->offline_since = Carbon::now();
|
||||
$twitchWebhook->save();
|
||||
}
|
||||
|
||||
if ($twitchWebhook->webhookActions) {
|
||||
// TODO: move this (generation of the message embed) to a separate method
|
||||
$userInfo = Twitch::getUserById(intval($data["user_id"]))->data[0];
|
||||
$gameInfo = null;
|
||||
|
||||
if ($data["game_id"]) {
|
||||
$gameInfo = Twitch::getGameById(intval($data["game_id"]))->data[0];
|
||||
}
|
||||
|
||||
$embed = new DiscordEmbedMessage();
|
||||
$embed->setUsername(config("app.name"))
|
||||
->setAvatar("https://tse1.mm.bing.net/th?id=OIP.kncd96A4OjouPDpT8ymkIAHaHa&pid=Api&f=1")
|
||||
->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) {
|
||||
|
||||
// TODO: change the url with the appropiate one
|
||||
$webhook = new DiscordWebhook($hookAction->discord_hook_url);
|
||||
$webhook->send($embed);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return response("Ok", 200);
|
||||
}
|
||||
|
||||
// TODO: implement the method
|
||||
public function subscription(Request $request) {
|
||||
if (!$this->verifySecret()) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
return "subscription";
|
||||
}
|
||||
|
||||
// TODO: verify that the method works ok
|
||||
private function verifySecret(String $secret = null) {
|
||||
if ($secret) {
|
||||
$X_HUB_SIGNATURE = null;
|
||||
|
||||
if (isset($_SERVER["HTTP_X_HUB_SIGNATURE"])) {
|
||||
$X_HUB_SIGNATURE = $_SERVER["HTTP_X_HUB_SIGNATURE"];
|
||||
}
|
||||
|
||||
$data = file_get_contents("php://input");
|
||||
$hmac = hash_hmac("sha256", $data, $secret);
|
||||
|
||||
return $hmac == $X_HUB_SIGNATURE;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
66
app/Http/Kernel.php
Normal file
66
app/Http/Kernel.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http;
|
||||
|
||||
use Illuminate\Foundation\Http\Kernel as HttpKernel;
|
||||
|
||||
class Kernel extends HttpKernel
|
||||
{
|
||||
/**
|
||||
* The application's global HTTP middleware stack.
|
||||
*
|
||||
* These middleware are run during every request to your application.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $middleware = [
|
||||
\App\Http\Middleware\TrustProxies::class,
|
||||
\Fruitcake\Cors\HandleCors::class,
|
||||
\App\Http\Middleware\CheckForMaintenanceMode::class,
|
||||
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
|
||||
\App\Http\Middleware\TrimStrings::class,
|
||||
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* The application's route middleware groups.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $middlewareGroups = [
|
||||
'web' => [
|
||||
\App\Http\Middleware\EncryptCookies::class,
|
||||
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
|
||||
\Illuminate\Session\Middleware\StartSession::class,
|
||||
// \Illuminate\Session\Middleware\AuthenticateSession::class,
|
||||
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
||||
\App\Http\Middleware\VerifyCsrfToken::class,
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
],
|
||||
|
||||
'api' => [
|
||||
'throttle:60,1',
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* The application's route middleware.
|
||||
*
|
||||
* These middleware may be assigned to groups or used individually.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $routeMiddleware = [
|
||||
'auth' => \App\Http\Middleware\Authenticate::class,
|
||||
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
|
||||
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
|
||||
'can' => \Illuminate\Auth\Middleware\Authorize::class,
|
||||
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
|
||||
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
|
||||
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
|
||||
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
||||
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
||||
];
|
||||
}
|
21
app/Http/Middleware/Authenticate.php
Normal file
21
app/Http/Middleware/Authenticate.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Auth\Middleware\Authenticate as Middleware;
|
||||
|
||||
class Authenticate extends Middleware
|
||||
{
|
||||
/**
|
||||
* Get the path the user should be redirected to when they are not authenticated.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return string|null
|
||||
*/
|
||||
protected function redirectTo($request)
|
||||
{
|
||||
if (! $request->expectsJson()) {
|
||||
return route("oauth.twitch.login");
|
||||
}
|
||||
}
|
||||
}
|
17
app/Http/Middleware/CheckForMaintenanceMode.php
Normal file
17
app/Http/Middleware/CheckForMaintenanceMode.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode as Middleware;
|
||||
|
||||
class CheckForMaintenanceMode extends Middleware
|
||||
{
|
||||
/**
|
||||
* The URIs that should be reachable while maintenance mode is enabled.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $except = [
|
||||
//
|
||||
];
|
||||
}
|
17
app/Http/Middleware/EncryptCookies.php
Normal file
17
app/Http/Middleware/EncryptCookies.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
|
||||
|
||||
class EncryptCookies extends Middleware
|
||||
{
|
||||
/**
|
||||
* The names of the cookies that should not be encrypted.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $except = [
|
||||
//
|
||||
];
|
||||
}
|
27
app/Http/Middleware/RedirectIfAuthenticated.php
Normal file
27
app/Http/Middleware/RedirectIfAuthenticated.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Providers\RouteServiceProvider;
|
||||
use Closure;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class RedirectIfAuthenticated
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @param string|null $guard
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle($request, Closure $next, $guard = null)
|
||||
{
|
||||
if (Auth::guard($guard)->check()) {
|
||||
return redirect(RouteServiceProvider::HOME);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
18
app/Http/Middleware/TrimStrings.php
Normal file
18
app/Http/Middleware/TrimStrings.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware;
|
||||
|
||||
class TrimStrings extends Middleware
|
||||
{
|
||||
/**
|
||||
* The names of the attributes that should not be trimmed.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $except = [
|
||||
'password',
|
||||
'password_confirmation',
|
||||
];
|
||||
}
|
27
app/Http/Middleware/TrustProxies.php
Normal file
27
app/Http/Middleware/TrustProxies.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Fideloper\Proxy\TrustProxies as Middleware;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TrustProxies extends Middleware
|
||||
{
|
||||
/**
|
||||
* The trusted proxies for this application.
|
||||
*
|
||||
* @var array|string
|
||||
*/
|
||||
protected $proxies = [
|
||||
"::1",
|
||||
"127.0.0.1",
|
||||
"192.168.1.5"
|
||||
];
|
||||
|
||||
/**
|
||||
* The headers that should be used to detect proxies.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $headers = Request::HEADER_X_FORWARDED_ALL;
|
||||
}
|
17
app/Http/Middleware/VerifyCsrfToken.php
Normal file
17
app/Http/Middleware/VerifyCsrfToken.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
|
||||
|
||||
class VerifyCsrfToken extends Middleware
|
||||
{
|
||||
/**
|
||||
* The URIs that should be excluded from CSRF verification.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $except = [
|
||||
"/hooks/*"
|
||||
];
|
||||
}
|
32
app/Http/Requests/AddWebhookAction.php
Normal file
32
app/Http/Requests/AddWebhookAction.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class AddWebhookAction 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 [
|
||||
"type" => "required|in:follow,stream,subscription",
|
||||
"discord_hook_url" => "required|url|starts_with:https://discordapp.com/api/webhooks/"
|
||||
];
|
||||
}
|
||||
}
|
32
app/Http/Requests/SubscribeWebhook.php
Normal file
32
app/Http/Requests/SubscribeWebhook.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class SubscribeWebhook 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 [
|
||||
"type" => "required|in:follow,stream,subscription",
|
||||
"mode" => "required|in:subscribe,unsubscribe"
|
||||
];
|
||||
}
|
||||
}
|
31
app/Http/Requests/TestWebhookAction.php
Normal file
31
app/Http/Requests/TestWebhookAction.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class TestWebhookAction extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return Auth::user();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
"discord_hook_url" => "required|url|starts_with:https://discordapp.com/api/webhooks/"
|
||||
];
|
||||
}
|
||||
}
|
30
app/Providers/AppServiceProvider.php
Normal file
30
app/Providers/AppServiceProvider.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Database\Schema\Builder;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
Builder::defaultStringLength(191);
|
||||
}
|
||||
}
|
30
app/Providers/AuthServiceProvider.php
Normal file
30
app/Providers/AuthServiceProvider.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
|
||||
class AuthServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* The policy mappings for the application.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $policies = [
|
||||
// 'App\Model' => 'App\Policies\ModelPolicy',
|
||||
];
|
||||
|
||||
/**
|
||||
* Register any authentication / authorization services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
$this->registerPolicies();
|
||||
|
||||
//
|
||||
}
|
||||
}
|
21
app/Providers/BroadcastServiceProvider.php
Normal file
21
app/Providers/BroadcastServiceProvider.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\Facades\Broadcast;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class BroadcastServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
Broadcast::routes();
|
||||
|
||||
require base_path('routes/channels.php');
|
||||
}
|
||||
}
|
34
app/Providers/EventServiceProvider.php
Normal file
34
app/Providers/EventServiceProvider.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Auth\Events\Registered;
|
||||
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
|
||||
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
|
||||
class EventServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* The event listener mappings for the application.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $listen = [
|
||||
Registered::class => [
|
||||
SendEmailVerificationNotification::class,
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Register any events for your application.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
parent::boot();
|
||||
|
||||
//
|
||||
}
|
||||
}
|
80
app/Providers/RouteServiceProvider.php
Normal file
80
app/Providers/RouteServiceProvider.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
class RouteServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* This namespace is applied to your controller routes.
|
||||
*
|
||||
* In addition, it is set as the URL generator's root namespace.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace = 'App\Http\Controllers';
|
||||
|
||||
/**
|
||||
* The path to the "home" route for your application.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const HOME = '/home';
|
||||
|
||||
/**
|
||||
* Define your route model bindings, pattern filters, etc.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
//
|
||||
|
||||
parent::boot();
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the routes for the application.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function map()
|
||||
{
|
||||
$this->mapApiRoutes();
|
||||
|
||||
$this->mapWebRoutes();
|
||||
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the "web" routes for the application.
|
||||
*
|
||||
* These routes all receive session state, CSRF protection, etc.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function mapWebRoutes()
|
||||
{
|
||||
Route::middleware('web')
|
||||
->namespace($this->namespace)
|
||||
->group(base_path('routes/web.php'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the "api" routes for the application.
|
||||
*
|
||||
* These routes are typically stateless.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function mapApiRoutes()
|
||||
{
|
||||
Route::prefix('api')
|
||||
->middleware('api')
|
||||
->namespace($this->namespace)
|
||||
->group(base_path('routes/api.php'));
|
||||
}
|
||||
}
|
37
app/TwitchWebhook.php
Normal file
37
app/TwitchWebhook.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class TwitchWebhook extends Model
|
||||
{
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
"type",
|
||||
"topic",
|
||||
"callback",
|
||||
"active",
|
||||
"expires_at",
|
||||
"disabled"
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the user that owns the access token.
|
||||
*/
|
||||
public function user() {
|
||||
return $this->belongsTo("App\User");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get webhook action
|
||||
*/
|
||||
public function webhookActions() {
|
||||
return $this->hasMany("App\WebhookAction", "webhook_id");
|
||||
}
|
||||
}
|
22
app/User.php
Normal file
22
app/User.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
|
||||
class User extends Authenticatable
|
||||
{
|
||||
/**
|
||||
* Get the access tokens of the user.
|
||||
*/
|
||||
public function accessTokens() {
|
||||
return $this->hasMany("App\UserAccessToken");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all configured twitch webhooks
|
||||
*/
|
||||
public function twitchWebhooks() {
|
||||
return $this->hasMany("App\TwitchWebhook");
|
||||
}
|
||||
}
|
33
app/UserAccessToken.php
Normal file
33
app/UserAccessToken.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class UserAccessToken extends Model
|
||||
{
|
||||
/**
|
||||
* The table associated with the model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = "users_access_tokens";
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
"access_token",
|
||||
"refresh_token",
|
||||
"expires_in"
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the user that owns the access token.
|
||||
*/
|
||||
public function user() {
|
||||
return $this->belongsTo("App\User");
|
||||
}
|
||||
}
|
25
app/WebhookAction.php
Normal file
25
app/WebhookAction.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class WebhookAction extends Model
|
||||
{
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
"discord_hook_url"
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the twitch webhook that executes this action.
|
||||
*/
|
||||
public function twitchWebhook() {
|
||||
return $this->belongsTo("App\TwitchWebhook", "webhook_id");
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user