Add matrix
This commit is contained in:
55
app/Http/Controllers/MatrixController.php
Normal file
55
app/Http/Controllers/MatrixController.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\MatrixRegisterRequest;
|
||||
use App\Services\MatrixRegistrationService;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class MatrixController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the user page.
|
||||
*/
|
||||
public function index(Request $request): \Illuminate\View\View
|
||||
{
|
||||
$rooms = [
|
||||
['name' => '🏠 General', 'description' => 'Our main chat.', 'alias' => 'https://matrix.to/#/#general:hstream.moe'],
|
||||
['name' => '📡 Releases', 'description' => 'Were we @everyone for new releases.', 'alias' => 'https://matrix.to/#/#releases:hstream.moe']
|
||||
];
|
||||
|
||||
return view('matrix.index', [
|
||||
'user' => $request->user(),
|
||||
'rooms' => $rooms,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create matrix user
|
||||
*/
|
||||
public function store(
|
||||
MatrixRegisterRequest $request,
|
||||
MatrixRegistrationService $matrixService
|
||||
) {
|
||||
try {
|
||||
$result = $matrixService->registerUser(
|
||||
$request->username,
|
||||
$request->password
|
||||
);
|
||||
|
||||
$user = $request->user();
|
||||
$user->matrix_id = $result['user_id'];
|
||||
$user->save();
|
||||
|
||||
return redirect()
|
||||
->back()
|
||||
->with('success', 'Matrix user created successfully.');
|
||||
} catch (\Exception $e) {
|
||||
return back()
|
||||
->withErrors([
|
||||
'username' => $e->getMessage()
|
||||
])
|
||||
->withInput();
|
||||
}
|
||||
}
|
||||
}
|
||||
49
app/Http/Requests/MatrixRegisterRequest.php
Normal file
49
app/Http/Requests/MatrixRegisterRequest.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class MatrixRegisterRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
$isOldEnough = $this->user()->created_at->lt(now()->subMonth());
|
||||
$noAccount = !$this->user()->matrix_id;
|
||||
return $isOldEnough && $noAccount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'username' => [
|
||||
'required',
|
||||
'string',
|
||||
'min:3',
|
||||
'max:32',
|
||||
'regex:/^[a-z0-9._=-]+$/', // Valid Matrix localpart
|
||||
],
|
||||
'password' => [
|
||||
'required',
|
||||
'string',
|
||||
'min:8',
|
||||
'confirmed',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'username.regex' => 'Username may only contain lowercase letters, numbers and . _ = -',
|
||||
];
|
||||
}
|
||||
}
|
||||
49
app/Services/MatrixRegistrationService.php
Normal file
49
app/Services/MatrixRegistrationService.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class MatrixRegistrationService
|
||||
{
|
||||
public function registerUser(string $username, string $password)
|
||||
{
|
||||
$server = config('services.matrix.server');
|
||||
$secret = config('services.matrix.shared_secret');
|
||||
|
||||
// Get nonce from Synapse
|
||||
$nonceResponse = Http::get("$server/_synapse/admin/v1/register");
|
||||
|
||||
if (!$nonceResponse->ok()) {
|
||||
throw new \Exception("Could not fetch nonce from Matrix.");
|
||||
}
|
||||
|
||||
$nonce = $nonceResponse->json()['nonce'];
|
||||
|
||||
// Generate MAC
|
||||
$mac = hash_hmac(
|
||||
'sha1',
|
||||
$nonce . "\0" .
|
||||
$username . "\0" .
|
||||
$password . "\0" .
|
||||
"notadmin",
|
||||
$secret
|
||||
);
|
||||
|
||||
// Send registration request
|
||||
$response = Http::post("$server/_synapse/admin/v1/register", [
|
||||
'nonce' => $nonce,
|
||||
'username' => $username,
|
||||
'password' => $password,
|
||||
'admin' => false,
|
||||
'mac' => $mac,
|
||||
]);
|
||||
|
||||
if ($response->failed()) {
|
||||
$error = $response->json()['error'] ?? $response->body();
|
||||
throw new \Exception($error);
|
||||
}
|
||||
|
||||
return $response->json();
|
||||
}
|
||||
}
|
||||
@@ -47,5 +47,11 @@ return [
|
||||
'avatar_default_extension' => env('DISCORD_EXTENSION_DEFAULT', 'webp'), // only pick from jpg, png, webp
|
||||
],
|
||||
|
||||
|
||||
/**
|
||||
* Matrix Registration
|
||||
*/
|
||||
'matrix' => [
|
||||
'server' => env('MATRIX_SERVER'),
|
||||
'shared_secret' => env('MATRIX_SHARED_SECRET'),
|
||||
],
|
||||
];
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->string('matrix_id')
|
||||
->nullable()
|
||||
->after('discord_id');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropColumn('matrix_id');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -49,6 +49,22 @@
|
||||
<i class="fa-brands fa-discord"></i> {{ __('nav.our-discord-server') }}
|
||||
</x-dropdown-link>
|
||||
|
||||
<x-dropdown-link :href="route('join.matrix')">
|
||||
<div class="flex items-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
|
||||
class="icon icon-tabler icons-tabler-outline icon-tabler-brand-matrix pr-1">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M4 3h-1v18h1" />
|
||||
<path d="M20 21h1v-18h-1" />
|
||||
<path d="M7 9v6" />
|
||||
<path d="M12 15v-3.5a2.5 2.5 0 1 0 -5 0v.5" />
|
||||
<path d="M17 15v-3.5a2.5 2.5 0 1 0 -5 0v.5" />
|
||||
</svg>
|
||||
Join our Matrix
|
||||
</div>
|
||||
</x-dropdown-link>
|
||||
|
||||
<x-dropdown-link>
|
||||
<div class="grid grid-cols-2">
|
||||
<p class="cursor-default">{{ __('nav.theme') }}</p>
|
||||
|
||||
149
resources/views/matrix/index.blade.php
Normal file
149
resources/views/matrix/index.blade.php
Normal file
@@ -0,0 +1,149 @@
|
||||
<x-app-layout>
|
||||
<div class="min-h-screen">
|
||||
<div class="max-w-5xl mx-auto py-16 px-6">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="text-center mb-8">
|
||||
<h1 class="text-4xl font-bold text-gray-900 dark:text-white mb-4">
|
||||
We're Moving to Matrix 🚀
|
||||
</h1>
|
||||
<p class="text-lg text-gray-600 dark:text-neutral-200 max-w-3xl mx-auto">
|
||||
Due to recent changes with Discord, we are transitioning our community to
|
||||
<span class="font-semibold text-indigo-600">Matrix</span> —
|
||||
an open, decentralized communication network.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Why Matrix -->
|
||||
<div class="bg-white dark:bg-neutral-800 rounded-2xl shadow-sm p-8 mb-8">
|
||||
<h2 class="text-2xl dark:text-white font-semibold mb-4">What is Matrix?</h2>
|
||||
|
||||
<p class="text-gray-700 dark:text-neutral-300 mb-4">
|
||||
Matrix is an open-source messaging system. Unlike Discord, it is
|
||||
<strong>decentralized</strong>. That means no single company controls it.
|
||||
</p>
|
||||
|
||||
<ul class="list-disc pl-6 text-gray-700 dark:text-neutral-300 space-y-2">
|
||||
<li>You can choose which server (called a “homeserver”) you register on.</li>
|
||||
<li>All servers communicate with each other (this is called federation).</li>
|
||||
<li>You are <strong>not required</strong> to use our server to join our rooms.</li>
|
||||
</ul>
|
||||
|
||||
<div class="mt-6 p-4 bg-pink-50 dark:bg-pink-950 border border-pink-100 dark:border-pink-700 rounded-lg">
|
||||
<p class="text-pink-500 text-sm">
|
||||
Example: You can register at a different server like matrix.org and still join our rooms.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Our Server -->
|
||||
<div class="bg-white dark:bg-neutral-800 rounded-2xl shadow-sm p-8 mb-8">
|
||||
<h2 class="text-2xl dark:text-white font-semibold mb-4">Our Matrix Server</h2>
|
||||
|
||||
<p class="text-gray-700 dark:text-neutral-300 mb-4">
|
||||
We provide our own Matrix homeserver for community members.
|
||||
</p>
|
||||
|
||||
<ul class="list-disc pl-6 text-gray-700 dark:text-neutral-300 space-y-2 mb-6">
|
||||
<li>Available to users registered for more than 1 month</li>
|
||||
<li>Fully federated with the entire Matrix network</li>
|
||||
<li>No obligation to use it — it’s optional</li>
|
||||
</ul>
|
||||
|
||||
@auth
|
||||
@if(auth()->user()->created_at->lt(now()->subMonth()))
|
||||
@if(auth()->user()->matrix_id)
|
||||
<div class="bg-green-50 dark:bg-green-950 dark:border-green-700 border border-green-200 p-6 rounded-xl">
|
||||
<h3 class="font-semibold text-green-800 dark:text-green-300 mb-2">
|
||||
✅ You are registered!
|
||||
</h3>
|
||||
<p class="text-green-700 dark:text-green-400 mb-4">
|
||||
Your Matrix account has been created successfully.
|
||||
</p>
|
||||
<p class="text-green-700 dark:text-green-400 mb-4">
|
||||
Make sure to store your password, as we don't have a password reset function!
|
||||
</p>
|
||||
<p class="text-green-700 dark:text-green-400 mb-4">
|
||||
You can now log in using any Matrix client of your choice.
|
||||
</p>
|
||||
<p class="text-green-700 dark:text-green-400 mb-4">
|
||||
For the best experience, we recommend:
|
||||
</p>
|
||||
<ul class="list-disc text-green-700 dark:text-green-400 pl-6 space-y-2 mb-6">
|
||||
<li>Downloading the official <strong><a href="https://element.io/download" target="_blank" class="underline">Element</a></strong> app for desktop or mobile</li>
|
||||
<li>Or using our web client via <strong><a href="https://element.hstream.moe/" target="_blank" class="underline">Element Web</a></strong></li>
|
||||
</ul>
|
||||
<p class="text-green-700 dark:text-green-400 mb-4">
|
||||
Simply sign in with your new username and password to get started.
|
||||
</p>
|
||||
</div>
|
||||
@else
|
||||
<div class="bg-green-50 dark:bg-green-950 dark:border-green-700 border border-green-200 p-6 rounded-xl">
|
||||
<h3 class="font-semibold text-green-800 dark:text-green-300 mb-2">
|
||||
🎉 You are eligible!
|
||||
</h3>
|
||||
<p class="text-green-700 dark:text-green-400 mb-4">
|
||||
Your account is older than one month. You can create your account now.
|
||||
</p>
|
||||
|
||||
@include('matrix.register')
|
||||
</div>
|
||||
@endif
|
||||
@else
|
||||
<div class="bg-yellow-50 dark:bg-yellow-900 border border-yellow-200 dark:border-yellow-700 p-6 rounded-xl">
|
||||
<h3 class="font-semibold text-yellow-800 dark:text-yellow-300 mb-2">
|
||||
⏳ Not Yet Eligible
|
||||
</h3>
|
||||
<p class="text-yellow-700 dark:text-yellow-400">
|
||||
Your account must be at least one month old to register
|
||||
on our Matrix server.
|
||||
</p>
|
||||
</div>
|
||||
@endif
|
||||
@else
|
||||
<div class="bg-gray-100 dark:bg-neutral-700 p-6 rounded-xl text-center">
|
||||
<p class="text-gray-700 dark:text-gray-100">
|
||||
Please log in to check if you're eligible for our Matrix server.
|
||||
</p>
|
||||
</div>
|
||||
@endauth
|
||||
</div>
|
||||
|
||||
<!-- Our Space -->
|
||||
<div class="bg-white dark:bg-neutral-800 rounded-2xl shadow-sm p-8 mb-8">
|
||||
<h2 class="text-2xl dark:text-white font-semibold mb-4">Our Matrix Space</h2>
|
||||
|
||||
<p class="text-gray-700 dark:text-neutral-300 mb-4">
|
||||
All of our rooms are organized inside our main Matrix Space:
|
||||
</p>
|
||||
|
||||
<div class="bg-gray-100 dark:bg-neutral-700 dark:text-neutral-300 p-4 rounded-lg font-mono text-sm break-all">
|
||||
<a href="https://matrix.to/#/#hstream:hstream.moe" target="_blank">https://matrix.to/#/#hstream:hstream.moe</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Room List -->
|
||||
<div class="bg-white dark:bg-neutral-800 rounded-2xl shadow-sm p-8">
|
||||
<h2 class="text-2xl dark:text-white font-semibold mb-6">Our Matrix Rooms</h2>
|
||||
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
@foreach($rooms as $room)
|
||||
<div class="border dark:border-neutral-900 rounded-xl p-6 hover:shadow-md dark:bg-neutral-800 hover:dark:bg-neutral-900/60 transition">
|
||||
<h3 class="font-semibold dark:text-neutral-100 text-lg mb-2">
|
||||
{{ $room['name'] }}
|
||||
</h3>
|
||||
|
||||
<p class="text-gray-600 dark:text-neutral-300 text-sm mb-4">
|
||||
{{ $room['description'] }}
|
||||
</p>
|
||||
|
||||
<div class="bg-gray-100 dark:bg-neutral-700 dark:text-neutral-300 p-3 rounded text-xs font-mono break-all">
|
||||
<a href="{{ $room['alias'] }}" target="_blank">{{ $room['alias'] }}</a>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
31
resources/views/matrix/register.blade.php
Normal file
31
resources/views/matrix/register.blade.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<form method="POST" action="{{ route('join.matrix.create') }}">
|
||||
@csrf
|
||||
<div>
|
||||
<x-input-label for="username" :value="__('Username')" />
|
||||
<x-text-input id="username" class="block mt-1 w-full" type="text" name="username" :value="old('username')" required autofocus autocomplete="username" />
|
||||
<x-input-error :messages="$errors->get('username')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<x-input-label for="password" :value="__('Password')" />
|
||||
<x-text-input id="password" class="block mt-1 w-full"
|
||||
type="password"
|
||||
name="password"
|
||||
required/>
|
||||
<x-input-error :messages="$errors->get('password')" class="mt-2" />
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<x-input-label for="password_confirmation" :value="__('Confirm Password')" />
|
||||
<x-text-input id="password_confirmation" class="block mt-1 w-full"
|
||||
type="password"
|
||||
name="password_confirmation"
|
||||
required />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end mt-4">
|
||||
<x-primary-button class="ms-4">
|
||||
{{ __('Create Matrix User') }}
|
||||
</x-primary-button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -11,7 +11,14 @@ use Illuminate\Support\Facades\Route;
|
||||
| User Routes
|
||||
|---------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
// Matrix
|
||||
Route::get('/join-matrix', [App\Http\Controllers\MatrixController::class, 'index'])->name('join.matrix');
|
||||
|
||||
Route::middleware('auth')->group(function () {
|
||||
// Matrix
|
||||
Route::post('/join-matrix', [App\Http\Controllers\MatrixController::class, 'store'])->name('join.matrix.create');
|
||||
|
||||
Route::get('/user/profile', [ProfileController::class, 'index'])->name('profile.show');
|
||||
Route::get('/user/comments', [ProfileController::class, 'comments'])->name('profile.comments');
|
||||
Route::get('/user/likes', [ProfileController::class, 'likes'])->name('profile.likes');
|
||||
|
||||
Reference in New Issue
Block a user