From aa498af07390624bc9776c848c76377f47626861 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Sun, 26 Jun 2022 16:06:08 -0600 Subject: [PATCH] Add live stream events --- app/Events/LiveStream/BanUser.php | 51 ++++++ app/Events/LiveStream/DeleteChatComment.php | 51 ++++++ app/Events/LiveStream/NewChatComment.php | 51 ++++++ app/Events/LiveStream/PinChatMessage.php | 51 ++++++ app/Events/LiveStream/UnpinChatMessage.php | 51 ++++++ app/Http/Controllers/Api/ApiV1Controller.php | 10 ++ app/Http/Controllers/LiveStreamController.php | 154 +++++++++++++++--- config/app.php | 3 +- config/broadcasting.php | 12 +- routes/api.php | 5 + routes/channels.php | 8 + 11 files changed, 416 insertions(+), 31 deletions(-) create mode 100644 app/Events/LiveStream/BanUser.php create mode 100644 app/Events/LiveStream/DeleteChatComment.php create mode 100644 app/Events/LiveStream/NewChatComment.php create mode 100644 app/Events/LiveStream/PinChatMessage.php create mode 100644 app/Events/LiveStream/UnpinChatMessage.php diff --git a/app/Events/LiveStream/BanUser.php b/app/Events/LiveStream/BanUser.php new file mode 100644 index 000000000..c9fc73628 --- /dev/null +++ b/app/Events/LiveStream/BanUser.php @@ -0,0 +1,51 @@ +livestream = $livestream; + $this->profileId = $profileId; + } + + /** + * Get the channels the event should broadcast on. + * + * @return \Illuminate\Broadcasting\Channel|array + */ + public function broadcastOn() + { + return new PrivateChannel('live.chat.' . $this->livestream->profile_id); + } + + public function broadcastAs() + { + return 'chat.ban-user'; + } + + public function broadcastWith() + { + return ['id' => $this->profileId]; + } +} diff --git a/app/Events/LiveStream/DeleteChatComment.php b/app/Events/LiveStream/DeleteChatComment.php new file mode 100644 index 000000000..c5987fb6b --- /dev/null +++ b/app/Events/LiveStream/DeleteChatComment.php @@ -0,0 +1,51 @@ +livestream = $livestream; + $this->chatmsg = $chatmsg; + } + + /** + * Get the channels the event should broadcast on. + * + * @return \Illuminate\Broadcasting\Channel|array + */ + public function broadcastOn() + { + return new PrivateChannel('live.chat.' . $this->livestream->profile_id); + } + + public function broadcastAs() + { + return 'chat.delete-message'; + } + + public function broadcastWith() + { + return ['id' => $this->chatmsg['id']]; + } +} diff --git a/app/Events/LiveStream/NewChatComment.php b/app/Events/LiveStream/NewChatComment.php new file mode 100644 index 000000000..1652a2366 --- /dev/null +++ b/app/Events/LiveStream/NewChatComment.php @@ -0,0 +1,51 @@ +livestream = $livestream; + $this->chatmsg = $chatmsg; + } + + /** + * Get the channels the event should broadcast on. + * + * @return \Illuminate\Broadcasting\Channel|array + */ + public function broadcastOn() + { + return new PrivateChannel('live.chat.' . $this->livestream->profile_id); + } + + public function broadcastAs() + { + return 'chat.new-message'; + } + + public function broadcastWith() + { + return ['msg' => $this->chatmsg]; + } +} diff --git a/app/Events/LiveStream/PinChatMessage.php b/app/Events/LiveStream/PinChatMessage.php new file mode 100644 index 000000000..1f0a624a7 --- /dev/null +++ b/app/Events/LiveStream/PinChatMessage.php @@ -0,0 +1,51 @@ +livestream = $livestream; + $this->chatmsg = $chatmsg; + } + + /** + * Get the channels the event should broadcast on. + * + * @return \Illuminate\Broadcasting\Channel|array + */ + public function broadcastOn() + { + return new PrivateChannel('live.chat.' . $this->livestream->profile_id); + } + + public function broadcastAs() + { + return 'chat.pin-message'; + } + + public function broadcastWith() + { + return $this->chatmsg; + } +} diff --git a/app/Events/LiveStream/UnpinChatMessage.php b/app/Events/LiveStream/UnpinChatMessage.php new file mode 100644 index 000000000..4a0d41243 --- /dev/null +++ b/app/Events/LiveStream/UnpinChatMessage.php @@ -0,0 +1,51 @@ +livestream = $livestream; + $this->chatmsg = $chatmsg; + } + + /** + * Get the channels the event should broadcast on. + * + * @return \Illuminate\Broadcasting\Channel|array + */ + public function broadcastOn() + { + return new PrivateChannel('live.chat.' . $this->livestream->profile_id); + } + + public function broadcastAs() + { + return 'chat.unpin-message'; + } + + public function broadcastWith() + { + return $this->chatmsg; + } +} diff --git a/app/Http/Controllers/Api/ApiV1Controller.php b/app/Http/Controllers/Api/ApiV1Controller.php index bd864ec7e..74a63d876 100644 --- a/app/Http/Controllers/Api/ApiV1Controller.php +++ b/app/Http/Controllers/Api/ApiV1Controller.php @@ -101,6 +101,16 @@ class ApiV1Controller extends Controller return response()->json($res, $code, $headers, JSON_UNESCAPED_SLASHES); } + public function getWebsocketConfig() + { + return config('broadcasting.default') === 'pusher' ? [ + 'host' => config('broadcasting.connections.pusher.options.host'), + 'port' => config('broadcasting.connections.pusher.options.port'), + 'key' => config('broadcasting.connections.pusher.key'), + 'cluster' => config('broadcasting.connections.pusher.options.cluster') + ] : []; + } + public function getApp(Request $request) { if(!$request->user()) { diff --git a/app/Http/Controllers/LiveStreamController.php b/app/Http/Controllers/LiveStreamController.php index ecc999770..71b6cb7c1 100644 --- a/app/Http/Controllers/LiveStreamController.php +++ b/app/Http/Controllers/LiveStreamController.php @@ -9,6 +9,12 @@ use Illuminate\Support\Facades\Storage; use App\Services\AccountService; use App\Services\FollowerService; use App\Services\LiveStreamService; +use App\User; +use App\Events\LiveStream\NewChatComment; +use App\Events\LiveStream\DeleteChatComment; +use App\Events\LiveStream\BanUser; +use App\Events\LiveStream\PinChatMessage; +use App\Events\LiveStream\UnpinChatMessage; class LiveStreamController extends Controller { @@ -63,32 +69,22 @@ class LiveStreamController extends Controller abort_if(!config('livestreaming.enabled'), 400); abort_if(!$request->user(), 403); - $stream = LiveStream::whereProfileId($request->input('profile_id'))->first(); + $stream = LiveStream::whereProfileId($request->input('profile_id')) + ->whereNotNull('live_at') + ->orderByDesc('live_at') + ->first(); if(!$stream) { return []; } $res = []; - $owner = $stream->profile_id == $request->user()->profile_id; + $owner = $request->user() ? $stream->profile_id == $request->user()->profile_id : false; if($stream->visibility === 'private') { abort_if(!$owner && !FollowerService::follows($request->user()->profile_id, $stream->profile_id), 403, 'LSE:011'); } - if($owner) { - $res['stream_key'] = $stream->stream_key; - $res['stream_id'] = $stream->stream_id; - $res['stream_url'] = $stream->getStreamKeyUrl(); - } - - if($stream->live_at == null) { - $res['hls_url'] = null; - $res['name'] = $stream->name; - $res['description'] = $stream->description; - return $res; - } - $res = [ 'hls_url' => $stream->getHlsUrl(), 'name' => $stream->name, @@ -98,6 +94,47 @@ class LiveStreamController extends Controller return response()->json($res, 200, [], JSON_UNESCAPED_SLASHES); } + + public function getUserStreamAsGuest(Request $request) + { + abort_if(!config('livestreaming.enabled'), 400); + + $stream = LiveStream::whereProfileId($request->input('profile_id')) + ->whereVisibility('public') + ->whereNotNull('live_at') + ->orderByDesc('live_at') + ->first(); + + if(!$stream) { + return []; + } + + $res = []; + + $res = [ + 'hls_url' => $stream->getHlsUrl(), + 'name' => $stream->name, + 'description' => $stream->description + ]; + + return response()->json($res, 200, [], JSON_UNESCAPED_SLASHES); + } + + public function showProfilePlayer(Request $request, $username) + { + abort_if(!config('livestreaming.enabled'), 400); + + $user = User::whereUsername($username)->firstOrFail(); + $id = (string) $user->profile_id; + $stream = LiveStream::whereProfileId($id) + ->whereNotNull('live_at') + ->first(); + + abort_if(!$request->user() && $stream->visibility !== 'public', 404); + + return view('live.player', compact('id')); + } + public function deleteStream(Request $request) { abort_if(!config('livestreaming.enabled'), 400); @@ -118,7 +155,7 @@ class LiveStreamController extends Controller abort_if(!config('livestreaming.enabled'), 400); abort_if(!$request->user(), 403); - return LiveStream::whereVisibility('local')->whereNotNull('live_at')->get()->map(function($stream) { + return LiveStream::whereIn('visibility', ['local', 'public'])->whereNotNull('live_at')->get()->map(function($stream) { return [ 'account' => AccountService::get($stream->profile_id), 'stream_id' => $stream->stream_id @@ -162,22 +199,30 @@ class LiveStreamController extends Controller 'message' => 'required|max:140' ]); - $stream = LiveStream::whereProfileId($request->input('profile_id'))->firstOrFail(); + $stream = LiveStream::whereProfileId($request->input('profile_id')) + ->whereNotNull('live_at') + ->firstOrFail(); $owner = $stream->profile_id == $request->user()->profile_id; if($stream->visibility === 'private') { - abort_if(!$owner && !FollowerService::follows($request->user()->profile_id, $stream->profile_id), 403, 'LSE:022'); + abort_if(!$owner && !FollowerService::follows($request->user()->profile_id, $stream->profile_id), 403); } + $user = AccountService::get($request->user()->profile_id); + + abort_if(!$user, 422); + $res = [ + 'id' => (string) Str::uuid(), 'pid' => (string) $request->user()->profile_id, - 'username' => $request->user()->username, + 'avatar' => $user['avatar'], + 'username' => $user['username'], 'text' => $request->input('message'), 'ts' => now()->timestamp ]; LiveStreamService::addComment($stream->profile_id, json_encode($res, JSON_UNESCAPED_SLASHES)); - + NewChatComment::dispatch($stream, $res); return $res; } @@ -209,14 +254,79 @@ class LiveStreamController extends Controller 'message' => 'required' ]); - abort_if($request->user()->profile_id != $request->input('profile_id'), 403); + $uid = $request->user()->profile_id; + $pid = $request->input('profile_id'); + $msg = $request->input('message'); + $admin = $uid == $request->input('profile_id'); + $owner = $uid == $msg['pid']; + abort_if(!$admin && !$owner, 403); - $stream = LiveStream::whereProfileId($request->user()->profile_id)->firstOrFail(); + $stream = LiveStream::whereProfileId($pid)->firstOrFail(); $payload = $request->input('message'); + broadcast(new DeleteChatComment($stream, $payload))->toOthers(); $payload = json_encode($payload, JSON_UNESCAPED_SLASHES); LiveStreamService::deleteComment($stream->profile_id, $payload); + return; + } + public function banChatUser(Request $request) + { + abort_if(!config('livestreaming.enabled'), 400); + abort_if(!$request->user(), 403); + + $this->validate($request, [ + 'profile_id' => 'required|exists:profiles,id', + ]); + + abort_if($request->user()->profile_id == $request->input('profile_id'), 403); + + $stream = LiveStream::whereProfileId($request->user()->profile_id)->firstOrFail(); + $pid = $request->input('profile_id'); + + BanUser::dispatch($stream, $pid); + return; + } + + public function pinChatComment(Request $request) + { + abort_if(!config('livestreaming.enabled'), 400); + abort_if(!$request->user(), 403); + + $this->validate($request, [ + 'profile_id' => 'required|exists:profiles,id', + 'message' => 'required' + ]); + + $uid = $request->user()->profile_id; + $pid = $request->input('profile_id'); + $msg = $request->input('message'); + + abort_if($uid != $pid, 403); + + $stream = LiveStream::whereProfileId($request->user()->profile_id)->firstOrFail(); + PinChatMessage::dispatch($stream, $msg); + return; + } + + public function unpinChatComment(Request $request) + { + abort_if(!config('livestreaming.enabled'), 400); + abort_if(!$request->user(), 403); + + $this->validate($request, [ + 'profile_id' => 'required|exists:profiles,id', + 'message' => 'required' + ]); + + $uid = $request->user()->profile_id; + $pid = $request->input('profile_id'); + $msg = $request->input('message'); + + abort_if($uid != $pid, 403); + + $stream = LiveStream::whereProfileId($request->user()->profile_id)->firstOrFail(); + UnpinChatMessage::dispatch($stream, $msg); return; } diff --git a/config/app.php b/config/app.php index 93273b625..516f428ef 100644 --- a/config/app.php +++ b/config/app.php @@ -154,10 +154,11 @@ return [ */ App\Providers\AppServiceProvider::class, App\Providers\AuthServiceProvider::class, - // App\Providers\BroadcastServiceProvider::class, + App\Providers\BroadcastServiceProvider::class, App\Providers\HorizonServiceProvider::class, App\Providers\EventServiceProvider::class, App\Providers\RouteServiceProvider::class, + App\Providers\TelescopeServiceProvider::class, App\Providers\PassportServiceProvider::class, ], diff --git a/config/broadcasting.php b/config/broadcasting.php index c4e40c97e..ff4cdfa70 100644 --- a/config/broadcasting.php +++ b/config/broadcasting.php @@ -37,14 +37,10 @@ return [ 'app_id' => env('PUSHER_APP_ID'), 'options' => [ 'cluster' => env('PUSHER_APP_CLUSTER'), - 'encrypted' => true, - 'host' => env('APP_DOMAIN'), - 'port' => 6001, - 'scheme' => 'https', - 'curl_options' => [ - CURLOPT_SSL_VERIFYHOST => 0, - CURLOPT_SSL_VERIFYPEER => 0, - ] + 'encrypted' => env('PUSHER_APP_ENCRYPTED', false), + 'host' => env('PUSHER_HOST', env('APP_DOMAIN')), + 'port' => env('PUSHER_PORT', 443), + 'scheme' => env('PUSHER_SCHEME', 'https') ], ], diff --git a/routes/api.php b/routes/api.php index 00fc628b4..3459a68e7 100644 --- a/routes/api.php +++ b/routes/api.php @@ -94,6 +94,7 @@ Route::group(['prefix' => 'api'], function() use($middleware) { Route::group(['prefix' => 'v2'], function() use($middleware) { Route::get('search', 'Api\ApiV1Controller@searchV2')->middleware($middleware); Route::post('media', 'Api\ApiV1Controller@mediaUploadV2')->middleware($middleware); + Route::get('streaming/config', 'Api\ApiV1Controller@getWebsocketConfig'); }); Route::group(['prefix' => 'live'], function() use($middleware) { @@ -101,10 +102,14 @@ Route::group(['prefix' => 'api'], function() use($middleware) { Route::post('stream/edit', 'LiveStreamController@editStream')->middleware($middleware); Route::get('active/list', 'LiveStreamController@getActiveStreams')->middleware($middleware); Route::get('accounts/stream', 'LiveStreamController@getUserStream')->middleware($middleware); + Route::get('accounts/stream/guest', 'LiveStreamController@getUserStreamAsGuest'); Route::delete('accounts/stream', 'LiveStreamController@deleteStream')->middleware($middleware); Route::get('chat/latest', 'LiveStreamController@getLatestChat')->middleware($middleware); Route::post('chat/message', 'LiveStreamController@addChatComment')->middleware($middleware); Route::post('chat/delete', 'LiveStreamController@deleteChatComment')->middleware($middleware); + Route::post('chat/ban-user', 'LiveStreamController@banChatUser')->middleware($middleware); + Route::post('chat/pin', 'LiveStreamController@pinChatComment')->middleware($middleware); + Route::post('chat/unpin', 'LiveStreamController@unpinChatComment')->middleware($middleware); Route::get('config', 'LiveStreamController@getConfig')->middleware($middleware); Route::post('broadcast/publish', 'LiveStreamController@clientBroadcastPublish'); Route::post('broadcast/finish', 'LiveStreamController@clientBroadcastFinish'); diff --git a/routes/channels.php b/routes/channels.php index f16a20b9b..e1ad75813 100644 --- a/routes/channels.php +++ b/routes/channels.php @@ -14,3 +14,11 @@ Broadcast::channel('App.User.{id}', function ($user, $id) { return (int) $user->id === (int) $id; }); + +Broadcast::channel('live.chat.{id}', function ($user, $id) { + return true; +}, ['guards' => ['web', 'api']]); + +Broadcast::channel('live.presence.{id}', function ($user, $id) { + return [ $user->profile_id ]; +}, ['guards' => ['web', 'api']]);