Link to viewer profile from actions list
This commit is contained in:
parent
64fd2596e7
commit
e45fb69b6a
@ -6,35 +6,50 @@
|
||||
use App\Http\Requests\ViewerRequest;
|
||||
|
||||
use App\Models\Trace\Message;
|
||||
use App\Models\Trace\ModeratorAction;
|
||||
|
||||
class ViewerController extends Controller
|
||||
{
|
||||
public function index(ViewerRequest $request, string $viewerId)
|
||||
public function index(ViewerRequest $request, string $viewer)
|
||||
{
|
||||
// Check if viewerId is numeric
|
||||
if (!is_numeric($viewerId)) {
|
||||
$message = Message::orderBy('timestamp', 'desc')
|
||||
->where('author_login', $viewerId)
|
||||
->firstOrFail();
|
||||
$viewerId = $message->author_id;
|
||||
}
|
||||
$viewer = strtolower($viewer);
|
||||
$message = Message::orderBy('timestamp', 'desc')
|
||||
->where('author_id', $viewer)
|
||||
->orWhere('author_login', $viewer)
|
||||
->firstOrFail();
|
||||
|
||||
// Check if viewerId exists
|
||||
$viewerId = $message->author_id;
|
||||
|
||||
// Get the latest message from the viewer
|
||||
$initialMessage = Message::orderBy('timestamp', 'desc')
|
||||
->where('author_id', $viewerId)
|
||||
->firstOrFail();
|
||||
|
||||
// Get distinct author_login based on author_id
|
||||
$usernames = Message::where('author_id', $viewerId)
|
||||
->distinct('author_login')
|
||||
->get()
|
||||
->orderBy('timestamp', 'desc')
|
||||
->pluck('author_login')
|
||||
->unique()
|
||||
->toArray();
|
||||
|
||||
$messages = [];
|
||||
foreach ($usernames as $username) {
|
||||
$messages[$username] = Message::where('author_id', $viewerId)
|
||||
->where('author_login', $username)
|
||||
->orderBy('timestamp', 'desc')
|
||||
->first();
|
||||
}
|
||||
|
||||
// Sort $messages based on value's timestamp attribute
|
||||
uasort($messages, function ($a, $b) {
|
||||
return $b->timestamp <=> $a->timestamp;
|
||||
});
|
||||
|
||||
return view('viewer.index', [
|
||||
'id' => $viewerId,
|
||||
'username' => $initialMessage->author_login,
|
||||
'usernames' => $usernames,
|
||||
'messages' => $messages,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ public function formatted($asHtml = false) : string
|
||||
* Only bans, unbans will be implicitly handled by default format.
|
||||
*/
|
||||
if ($cmd === 'ban') {
|
||||
return sprintf('/%s %s %s', $cmd, $this->targetName(), $this->reason ?? '');
|
||||
return sprintf('/%s %s %s', $cmd, $this->targetName($asHtml), $this->reason ?? '');
|
||||
}
|
||||
|
||||
if ($cmd === 'followers') {
|
||||
@ -43,7 +43,7 @@ public function formatted($asHtml = false) : string
|
||||
$message = htmlspecialchars($message);
|
||||
}
|
||||
|
||||
return sprintf('/%s %s %s %s', $cmd, $this->targetName(), $message, $this->message_id);
|
||||
return sprintf('/%s %s %s %s', $cmd, $this->targetName($asHtml), $message, $this->message_id);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -56,7 +56,7 @@ public function formatted($asHtml = false) : string
|
||||
$reason = htmlspecialchars($reason);
|
||||
}
|
||||
|
||||
return sprintf('/%s %s %s %s', $cmd, $this->targetName(), $duration, $reason ?? '');
|
||||
return sprintf('/%s %s %s %s', $cmd, $this->targetName($asHtml), $duration, $reason ?? '');
|
||||
}
|
||||
|
||||
if ($discriminator === 'TermAction') {
|
||||
@ -68,22 +68,39 @@ public function formatted($asHtml = false) : string
|
||||
return sprintf('/%s %s %s', $cmd, $text, $this->term_id);
|
||||
}
|
||||
|
||||
return trim(sprintf('/%s %s', $cmd, $this->targetName()));
|
||||
return trim(sprintf('/%s %s', $cmd, $this->targetName($asHtml)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the target of the action, if any
|
||||
*
|
||||
* @param bool $linkToViewerPage Returns an HTML link to the viewer page
|
||||
* @return string
|
||||
*/
|
||||
public function targetName() : ?string
|
||||
public function targetName($linkToViewerPage = false) : ?string
|
||||
{
|
||||
$format = '<a href="%s" class="text-amber-400" title="See viewer profile">%s</a>';
|
||||
|
||||
if (!empty($this->target_name)) {
|
||||
return $this->target_name;
|
||||
$name = $this->target_name;
|
||||
$id = $this->target_id ?? $name;
|
||||
|
||||
if (!$linkToViewerPage) {
|
||||
return $name;
|
||||
}
|
||||
|
||||
return sprintf($format, route('viewer', ['viewer' => $id]), $name);
|
||||
}
|
||||
|
||||
if (!empty($this->targeted_moderator_action_target_name)) {
|
||||
return $this->targeted_moderator_action_target_name;
|
||||
$name = $this->targeted_moderator_action_target_name;
|
||||
|
||||
if (!$linkToViewerPage) {
|
||||
return $name;
|
||||
}
|
||||
|
||||
$id = $this->target_id ?? $name;
|
||||
return sprintf($format, route('viewer', ['viewer' => $id]), $name);
|
||||
}
|
||||
|
||||
return '';
|
||||
|
@ -5,34 +5,42 @@
|
||||
</h2>
|
||||
</x-slot>
|
||||
|
||||
<div class="py-12">
|
||||
<div class="max-w-full mx-auto sm:px-6 lg:px-8">
|
||||
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="p-6 text-gray-900 dark:text-gray-100">
|
||||
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
|
||||
{{ $channel->username() }}
|
||||
</h2>
|
||||
<div class="mx-auto sm:px-6 lg:px-8">
|
||||
<div class="bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="p-6 text-gray-100">
|
||||
<h2 class="font-semibold text-xl text-gray-200 leading-tight">
|
||||
{{ $channel->username() }}
|
||||
</h2>
|
||||
|
||||
<table class="table-auto w-full">
|
||||
<thead>
|
||||
<table class="table-auto w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-4 py-2">Time & date</th>
|
||||
<th class="px-4 py-2">Command</th>
|
||||
<th class="px-4 py-2">Moderator</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($actions as $action)
|
||||
<tr>
|
||||
<th class="px-4 py-2">Time & date</th>
|
||||
<th class="px-4 py-2">Command</th>
|
||||
<th class="px-4 py-2">Moderator</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($actions as $action)
|
||||
<tr>
|
||||
<td class="border px-4 py-2" title="{{ $action->timestamp }}">{{ $action->timestamp }}</td>
|
||||
<td class="border px-4 py-2" title="{{ $action->timestamp }}">{{ $action->timestamp }}
|
||||
</td>
|
||||
@if (empty($action->targetName()))
|
||||
<td class="border px-4 py-2 break-all">
|
||||
<code>{{ $action->formatted() }}</code>
|
||||
</td>
|
||||
<td class="border px-4 py-2">{{ $action->user->login }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
@if ($actions->hasPages())
|
||||
@else
|
||||
<td class="border px-4 py-2 break-all">
|
||||
<code>
|
||||
{!! $action->formatted(true) !!}
|
||||
</code>
|
||||
</td>
|
||||
@endif
|
||||
<td class="border px-4 py-2">{{ $action->user->login }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
@if ($actions->hasPages())
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="3" class="border px-4 py-2">
|
||||
@ -40,9 +48,8 @@
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
@endif
|
||||
</table>
|
||||
</div>
|
||||
@endif
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -5,16 +5,14 @@
|
||||
</h2>
|
||||
</x-slot>
|
||||
|
||||
<div class="max-w-full mx-auto sm:px-6 lg:px-8">
|
||||
<div class="max-w-full mx-auto">
|
||||
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="p-6 text-gray-900 dark:text-gray-100">
|
||||
{{ __("You're logged in!") }}
|
||||
</div>
|
||||
<div class="mx-auto sm:px-6 lg:px-8">
|
||||
<div class="bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="p-6 text-gray-100">
|
||||
{{ __("Hi :user!", ['user' => Auth::user()->display_name]) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-6 text-gray-900 dark:text-gray-100">
|
||||
<div class="bg-gray-800 p-6 -mt-6 text-gray-100">
|
||||
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight mb-2">
|
||||
{{ __('Channels') }}
|
||||
</h2>
|
||||
|
@ -13,6 +13,8 @@
|
||||
|
||||
<!-- Scripts -->
|
||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||
|
||||
@yield('head')
|
||||
</head>
|
||||
<body class="font-sans antialiased bg-gray-900">
|
||||
<div class="flex flex-col h-screen justify-between">
|
||||
@ -20,7 +22,7 @@
|
||||
|
||||
<!-- Page Heading -->
|
||||
@if (isset($header))
|
||||
<header class="bg-white dark:bg-gray-800 shadow">
|
||||
<header class="bg-gray-800 shadow">
|
||||
<div class="max-w-full mx-auto py-6 px-4 sm:px-6 lg:px-8">
|
||||
{{ $header }}
|
||||
</div>
|
||||
@ -29,37 +31,41 @@
|
||||
|
||||
<div class="mt-4">
|
||||
<div class="max-w-full mx-auto sm:px-6 lg:px-8">
|
||||
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="p-6 text-gray-900 dark:text-gray-100">
|
||||
<div class="bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="p-6 text-gray-100">
|
||||
<p>This is the initial implementation of this project. A few different things that are planned:</p>
|
||||
|
||||
<ul class="list-disc list-inside mt-2">
|
||||
<li>Channels that are tracked where you have moderator access will be <a href="https://twitch.uservoice.com/forums/310213-developers/suggestions/38203849-add-endpoint-to-return-channels-where-the-user-is" class="underline text-amber-400">automatically retrieved from Twitch's API upon login</a>. At the moment I have to manually give permissions via the database.</li>
|
||||
<li>Channels that are tracked where you have moderator access will be <a href="https://twitch.uservoice.com/forums/310213-developers/suggestions/38203849-add-endpoint-to-return-channels-where-the-user-is" class="underline text-amber-400">automatically retrieved from Twitch's API upon login</a>. At the moment I have to manually give permissions via the database, since this API is currently only "planned" by Twitch.</li>
|
||||
<li>Different filtering options: Type of action (timeout, raid etc.), moderator (whodunit), user/viewer (next point).</li>
|
||||
<li>See related moderation actions, for example if a specific viewer has been timed out or banned multiple times.</li>
|
||||
<li>Searching by text.</li>
|
||||
<li>Linking any affected viewer's to their Twitch viewer card.</li>
|
||||
<li>Automatic conversion to local time. Currently it's displayed in UTC.</li>
|
||||
<li>Automatic conversion to local time. Currently time & date is displayed in UTC.</li>
|
||||
<li>Ability to see a viewer's chat messages at the time of action, alongside messages before/after to see context.</li>
|
||||
<li>Username history of a user</li>
|
||||
<li>... and probably more I'll figure out as I go along.</li>
|
||||
</ul>
|
||||
|
||||
<p class="mt-6">List of things that were planned and has been implemented in some form:</p>
|
||||
<ul class="list-disc list-inside mt-2">
|
||||
<li>Username history of a user - For any user that has been timed out, raided, etc. you can now click on their name (highlighted in orange/amber) and access their basic "viewer profile".</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Page Content -->
|
||||
<main class="mb-auto bg-white dark:bg-gray-800 mt-4">
|
||||
<main class="mb-auto mt-4">
|
||||
{{ $slot }}
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="bg-white dark:bg-gray-800 shadow bottom-0 mt-4">
|
||||
<footer class="bg-gray-800 shadow bottom-0 mt-4">
|
||||
<div class="max-w-full mx-auto py-6 px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">
|
||||
<p>TraceDash — v0.0.2 [Pre-Alpha Early Access™]</p>
|
||||
<p>TraceDash — v0.0.3 [Pre-Alpha Early Access™]</p>
|
||||
<p>Powered by an unhealthy amount of caffeine</p>
|
||||
</div>
|
||||
|
||||
|
@ -5,23 +5,23 @@
|
||||
</h2>
|
||||
</x-slot>
|
||||
|
||||
<div class="py-12">
|
||||
<div class="max-w-full mx-auto sm:px-6 lg:px-8">
|
||||
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="p-6 text-gray-900 dark:text-gray-100">
|
||||
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
|
||||
Viewer details for: {{ $username }} [{{ $id }}]
|
||||
</h2>
|
||||
<div class="max-w-full mx-auto sm:px-6 lg:px-8">
|
||||
<div class="bg-white dark:bg-gray-800 overflow-hidden shadow-sm sm:rounded-lg">
|
||||
<div class="p-6 text-gray-900 dark:text-gray-100">
|
||||
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
|
||||
Viewer details for: {{ $username }} [{{ $id }}]
|
||||
</h2>
|
||||
|
||||
<p class="mt-4">Seen under the following usernames:</p>
|
||||
<p class="mt-4">Seen under the following usernames:</p>
|
||||
|
||||
<!-- List style dots -->
|
||||
<ul class="mt-2 list-disc list-inside">
|
||||
@foreach ($usernames as $username)
|
||||
<li>{{ $username }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
<!-- List style dots -->
|
||||
<ul class="mt-2 list-disc list-inside">
|
||||
@foreach ($messages as $username => $message)
|
||||
<li class="mt-1">
|
||||
{{ $username }} <span class="text-gray-400">— Last seen with this username: {{ date('Y-m-d', strtotime($message->timestamp)) }}</span>
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -17,7 +17,7 @@
|
||||
Route::get('/channel/{channel}', [DashboardController::class, 'channel'])
|
||||
->name('dashboard.channel');
|
||||
|
||||
Route::get('/viewer/{viewerId}', [ViewerController::class, 'index'])
|
||||
Route::get('/viewer/{viewer}', [ViewerController::class, 'index'])
|
||||
->name('viewer');
|
||||
|
||||
Route::post('logout', [TwitchController::class, 'logout'])
|
||||
|
Loading…
Reference in New Issue
Block a user