From 9eeb7b67414963a9851bbfd52f7486c0fe1c92d9 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Tue, 19 Nov 2024 03:45:52 -0700 Subject: [PATCH] Update Status caption logic, stop storing duplicate html caption in db and defer to cached StatusService rendering --- app/Console/Commands/TransformImports.php | 50 +- app/Http/Controllers/Api/ApiV1Controller.php | 5 +- .../Controllers/Api/ApiV1Dot1Controller.php | 6 +- app/Http/Controllers/CommentController.php | 14 +- app/Http/Controllers/ComposeController.php | 14 +- .../Controllers/DirectMessageController.php | 12 +- .../Controllers/GroupFederationController.php | 170 ++-- .../Controllers/InternalApiController.php | 760 +++++++++--------- app/Http/Controllers/MicroController.php | 105 ++- app/Http/Controllers/SearchController.php | 13 +- .../Stories/StoryApiV1Controller.php | 3 +- .../Controllers/StoryComposeController.php | 4 +- app/Jobs/GroupPipeline/NewStatusPipeline.php | 181 ++--- app/Jobs/StatusPipeline/StatusEntityLexer.php | 7 +- .../StatusRemoteUpdatePipeline.php | 3 +- app/Services/Status/UpdateStatusService.php | 228 +++--- app/Util/ActivityPub/Helpers.php | 3 +- app/Util/ActivityPub/Inbox.php | 3 - 18 files changed, 776 insertions(+), 805 deletions(-) diff --git a/app/Console/Commands/TransformImports.php b/app/Console/Commands/TransformImports.php index a5a4dbb7a..6b6efa6e3 100644 --- a/app/Console/Commands/TransformImports.php +++ b/app/Console/Commands/TransformImports.php @@ -2,17 +2,16 @@ namespace App\Console\Commands; -use Illuminate\Console\Command; -use App\Models\ImportPost; -use App\Services\ImportService; use App\Media; +use App\Models\ImportPost; use App\Profile; -use App\Status; -use Storage; use App\Services\AccountService; +use App\Services\ImportService; use App\Services\MediaPathService; +use App\Status; +use Illuminate\Console\Command; use Illuminate\Support\Str; -use App\Util\Lexer\Autolink; +use Storage; class TransformImports extends Command { @@ -35,23 +34,24 @@ class TransformImports extends Command */ public function handle() { - if(!config('import.instagram.enabled')) { + if (! config('import.instagram.enabled')) { return; } $ips = ImportPost::whereNull('status_id')->where('skip_missing_media', '!=', true)->take(500)->get(); - if(!$ips->count()) { + if (! $ips->count()) { return; } - foreach($ips as $ip) { + foreach ($ips as $ip) { $id = $ip->user_id; $pid = $ip->profile_id; $profile = Profile::find($pid); - if(!$profile) { + if (! $profile) { $ip->skip_missing_media = true; $ip->save(); + continue; } @@ -63,39 +63,43 @@ class TransformImports extends Command ->where('creation_day', $ip->creation_day) ->exists(); - if($exists == true) { + if ($exists == true) { $ip->skip_missing_media = true; $ip->save(); + continue; } $idk = ImportService::getId($ip->user_id, $ip->creation_year, $ip->creation_month, $ip->creation_day); - if(!$idk) { + if (! $idk) { $ip->skip_missing_media = true; $ip->save(); + continue; } - if(Storage::exists('imports/' . $id . '/' . $ip->filename) === false) { + if (Storage::exists('imports/'.$id.'/'.$ip->filename) === false) { ImportService::clearAttempts($profile->id); ImportService::getPostCount($profile->id, true); $ip->skip_missing_media = true; $ip->save(); + continue; } $missingMedia = false; - foreach($ip->media as $ipm) { + foreach ($ip->media as $ipm) { $fileName = last(explode('/', $ipm['uri'])); - $og = 'imports/' . $id . '/' . $fileName; - if(!Storage::exists($og)) { + $og = 'imports/'.$id.'/'.$fileName; + if (! Storage::exists($og)) { $missingMedia = true; } } - if($missingMedia === true) { + if ($missingMedia === true) { $ip->skip_missing_media = true; $ip->save(); + continue; } @@ -103,7 +107,6 @@ class TransformImports extends Command $status = new Status; $status->profile_id = $pid; $status->caption = $caption; - $status->rendered = strlen(trim($caption)) ? Autolink::create()->autolink($ip->caption) : null; $status->type = $ip->post_type; $status->scope = 'unlisted'; @@ -112,20 +115,21 @@ class TransformImports extends Command $status->created_at = now()->parse($ip->creation_date); $status->save(); - foreach($ip->media as $ipm) { + foreach ($ip->media as $ipm) { $fileName = last(explode('/', $ipm['uri'])); $ext = last(explode('.', $fileName)); $basePath = MediaPathService::get($profile); - $og = 'imports/' . $id . '/' . $fileName; - if(!Storage::exists($og)) { + $og = 'imports/'.$id.'/'.$fileName; + if (! Storage::exists($og)) { $ip->skip_missing_media = true; $ip->save(); + continue; } $size = Storage::size($og); $mime = Storage::mimeType($og); - $newFile = Str::random(40) . '.' . $ext; - $np = $basePath . '/' . $newFile; + $newFile = Str::random(40).'.'.$ext; + $np = $basePath.'/'.$newFile; Storage::move($og, $np); $media = new Media; $media->profile_id = $pid; diff --git a/app/Http/Controllers/Api/ApiV1Controller.php b/app/Http/Controllers/Api/ApiV1Controller.php index e64759e24..0a2b88c2e 100644 --- a/app/Http/Controllers/Api/ApiV1Controller.php +++ b/app/Http/Controllers/Api/ApiV1Controller.php @@ -3490,8 +3490,7 @@ class ApiV1Controller extends Controller return []; } - $content = strip_tags($request->input('status')); - $rendered = Autolink::create()->autolink($content); + $content = $request->filled('status') ? strip_tags(Purify::clean($request->input('status'))) : null; $cw = $user->profile->cw == true ? true : $request->boolean('sensitive', false); $spoilerText = $cw && $request->filled('spoiler_text') ? $request->input('spoiler_text') : null; @@ -3505,7 +3504,6 @@ class ApiV1Controller extends Controller $status = new Status; $status->caption = $content; - $status->rendered = $rendered; $status->scope = $visibility; $status->visibility = $visibility; $status->profile_id = $user->profile_id; @@ -3530,7 +3528,6 @@ class ApiV1Controller extends Controller if (! $in_reply_to_id) { $status = new Status; $status->caption = $content; - $status->rendered = $rendered; $status->profile_id = $user->profile_id; $status->is_nsfw = $cw; $status->cw_summary = $spoilerText; diff --git a/app/Http/Controllers/Api/ApiV1Dot1Controller.php b/app/Http/Controllers/Api/ApiV1Dot1Controller.php index 38550e5fe..2002b06a2 100644 --- a/app/Http/Controllers/Api/ApiV1Dot1Controller.php +++ b/app/Http/Controllers/Api/ApiV1Dot1Controller.php @@ -37,7 +37,6 @@ use App\Status; use App\StatusArchived; use App\User; use App\UserSetting; -use App\Util\Lexer\Autolink; use App\Util\Lexer\RestrictedNames; use Cache; use DB; @@ -49,6 +48,7 @@ use Jenssegers\Agent\Agent; use League\Fractal; use League\Fractal\Serializer\ArraySerializer; use Mail; +use Purify; class ApiV1Dot1Controller extends Controller { @@ -1293,14 +1293,12 @@ class ApiV1Dot1Controller extends Controller return []; } - $content = strip_tags($request->input('status')); - $rendered = Autolink::create()->autolink($content); + $content = $request->filled('status') ? strip_tags(Purify::clean($request->input('status'))) : null; $cw = $user->profile->cw == true ? true : $request->boolean('sensitive', false); $spoilerText = $cw && $request->filled('spoiler_text') ? $request->input('spoiler_text') : null; $status = new Status; $status->caption = $content; - $status->rendered = $rendered; $status->profile_id = $user->profile_id; $status->is_nsfw = $cw; $status->cw_summary = $spoilerText; diff --git a/app/Http/Controllers/CommentController.php b/app/Http/Controllers/CommentController.php index 1dd985723..5e3f1ad7f 100644 --- a/app/Http/Controllers/CommentController.php +++ b/app/Http/Controllers/CommentController.php @@ -8,12 +8,12 @@ use App\Services\StatusService; use App\Status; use App\Transformer\Api\StatusTransformer; use App\UserFilter; -use App\Util\Lexer\Autolink; use Auth; use DB; use Illuminate\Http\Request; use League\Fractal; use League\Fractal\Serializer\ArraySerializer; +use Purify; class CommentController extends Controller { @@ -56,12 +56,10 @@ class CommentController extends Controller $reply = DB::transaction(function () use ($comment, $status, $profile, $nsfw) { $scope = $profile->is_private == true ? 'private' : 'public'; - $autolink = Autolink::create()->autolink($comment); - $reply = new Status(); + $reply = new Status; $reply->profile_id = $profile->id; $reply->is_nsfw = $nsfw; - $reply->caption = e($comment); - $reply->rendered = $autolink; + $reply->caption = Purify::clean($comment); $reply->in_reply_to_id = $status->id; $reply->in_reply_to_profile_id = $status->profile_id; $reply->scope = $scope; @@ -76,9 +74,9 @@ class CommentController extends Controller CommentPipeline::dispatch($status, $reply); if ($request->ajax()) { - $fractal = new Fractal\Manager(); - $fractal->setSerializer(new ArraySerializer()); - $entity = new Fractal\Resource\Item($reply, new StatusTransformer()); + $fractal = new Fractal\Manager; + $fractal->setSerializer(new ArraySerializer); + $entity = new Fractal\Resource\Item($reply, new StatusTransformer); $entity = $fractal->createData($entity)->toArray(); $response = [ 'code' => 200, diff --git a/app/Http/Controllers/ComposeController.php b/app/Http/Controllers/ComposeController.php index 480b9c8e4..4ea8e2267 100644 --- a/app/Http/Controllers/ComposeController.php +++ b/app/Http/Controllers/ComposeController.php @@ -25,7 +25,6 @@ use App\Services\UserStorageService; use App\Status; use App\Transformer\Api\MediaTransformer; use App\UserFilter; -use App\Util\Lexer\Autolink; use App\Util\Media\Filter; use App\Util\Media\License; use Auth; @@ -43,8 +42,8 @@ class ComposeController extends Controller public function __construct() { $this->middleware('auth'); - $this->fractal = new Fractal\Manager(); - $this->fractal->setSerializer(new ArraySerializer()); + $this->fractal = new Fractal\Manager; + $this->fractal->setSerializer(new ArraySerializer); } public function show(Request $request) @@ -112,14 +111,14 @@ class ComposeController extends Controller abort_if(MediaBlocklistService::exists($hash) == true, 451); - $media = new Media(); + $media = new Media; $media->status_id = null; $media->profile_id = $profile->id; $media->user_id = $user->id; $media->media_path = $path; $media->original_sha256 = $hash; $media->size = $photo->getSize(); - $media->caption = ""; + $media->caption = ''; $media->mime = $mime; $media->filter_class = $filterClass; $media->filter_name = $filterName; @@ -151,7 +150,7 @@ class ComposeController extends Controller $user->save(); Cache::forget($limitKey); - $resource = new Fractal\Resource\Item($media, new MediaTransformer()); + $resource = new Fractal\Resource\Item($media, new MediaTransformer); $res = $this->fractal->createData($resource)->toArray(); $res['preview_url'] = $preview_url; $res['url'] = $url; @@ -571,7 +570,6 @@ class ComposeController extends Controller } $status->caption = strip_tags($request->caption); - $status->rendered = Autolink::create()->autolink($status->caption); $status->scope = 'draft'; $status->visibility = 'draft'; $status->profile_id = $profile->id; @@ -693,7 +691,6 @@ class ComposeController extends Controller $status->visibility = $visibility; $status->scope = $visibility; $status->type = 'text'; - $status->rendered = Autolink::create()->autolink($status->caption); $status->entities = json_encode(array_merge([ 'timg' => [ 'version' => 0, @@ -806,7 +803,6 @@ class ComposeController extends Controller $status = new Status; $status->profile_id = $request->user()->profile_id; $status->caption = $request->input('caption'); - $status->rendered = Autolink::create()->autolink($status->caption); $status->visibility = 'draft'; $status->scope = 'draft'; $status->type = 'poll'; diff --git a/app/Http/Controllers/DirectMessageController.php b/app/Http/Controllers/DirectMessageController.php index 5b07f6adb..3f8d61036 100644 --- a/app/Http/Controllers/DirectMessageController.php +++ b/app/Http/Controllers/DirectMessageController.php @@ -22,6 +22,7 @@ use App\Services\WebfingerService; use App\Status; use App\UserFilter; use App\Util\ActivityPub\Helpers; +use App\Util\Lexer\Autolink; use Illuminate\Http\Request; use Illuminate\Support\Str; @@ -326,7 +327,6 @@ class DirectMessageController extends Controller $status = new Status; $status->profile_id = $profile->id; $status->caption = $msg; - $status->rendered = $msg; $status->visibility = 'direct'; $status->scope = 'direct'; $status->in_reply_to_profile_id = $recipient->id; @@ -636,7 +636,6 @@ class DirectMessageController extends Controller $status = new Status; $status->profile_id = $profile->id; $status->caption = null; - $status->rendered = null; $status->visibility = 'direct'; $status->scope = 'direct'; $status->in_reply_to_profile_id = $recipient->id; @@ -830,6 +829,11 @@ class DirectMessageController extends Controller { $profile = $dm->author; $url = $dm->recipient->sharedInbox ?? $dm->recipient->inbox_url; + $status = $dm->status; + + if (! $status) { + return; + } $tags = [ [ @@ -839,6 +843,8 @@ class DirectMessageController extends Controller ], ]; + $content = $status->caption ? Autolink::create()->autolink($status->caption) : null; + $body = [ '@context' => [ 'https://w3id.org/security/v1', @@ -854,7 +860,7 @@ class DirectMessageController extends Controller 'id' => $dm->status->url(), 'type' => 'Note', 'summary' => null, - 'content' => $dm->status->rendered ?? $dm->status->caption, + 'content' => $content, 'inReplyTo' => null, 'published' => $dm->status->created_at->toAtomString(), 'url' => $dm->status->url(), diff --git a/app/Http/Controllers/GroupFederationController.php b/app/Http/Controllers/GroupFederationController.php index 7f45f74a4..0e5879b01 100644 --- a/app/Http/Controllers/GroupFederationController.php +++ b/app/Http/Controllers/GroupFederationController.php @@ -2,102 +2,106 @@ namespace App\Http\Controllers; -use Illuminate\Http\Request; -use Illuminate\Support\Facades\Cache; use App\Models\Group; use App\Models\GroupPost; -use App\Status; use App\Models\InstanceActor; use App\Services\MediaService; +use App\Status; +use App\Util\Lexer\Autolink; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Cache; class GroupFederationController extends Controller { - public function getGroupObject(Request $request, $id) - { - $group = Group::whereLocal(true)->whereActivitypub(true)->findOrFail($id); - $res = $this->showGroupObject($group); - return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES); - } + public function getGroupObject(Request $request, $id) + { + $group = Group::whereLocal(true)->whereActivitypub(true)->findOrFail($id); + $res = $this->showGroupObject($group); - public function showGroupObject($group) - { - return Cache::remember('ap:groups:object:' . $group->id, 3600, function() use($group) { - return [ - '@context' => 'https://www.w3.org/ns/activitystreams', - 'id' => $group->url(), - 'inbox' => $group->permalink('/inbox'), - 'name' => $group->name, - 'outbox' => $group->permalink('/outbox'), - 'summary' => $group->description, - 'type' => 'Group', - 'attributedTo' => [ - 'type' => 'Person', - 'id' => $group->admin->permalink() - ], - // 'endpoints' => [ - // 'sharedInbox' => config('app.url') . '/f/inbox' - // ], - 'preferredUsername' => 'gid_' . $group->id, - 'publicKey' => [ - 'id' => $group->permalink('#main-key'), - 'owner' => $group->permalink(), - 'publicKeyPem' => InstanceActor::first()->public_key, - ], - 'url' => $group->permalink() - ]; + return response()->json($res, 200, [], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + } - if($group->metadata && isset($group->metadata['avatar'])) { - $res['icon'] = [ - 'type' => 'Image', - 'url' => $group->metadata['avatar']['url'] - ]; - } + public function showGroupObject($group) + { + return Cache::remember('ap:groups:object:'.$group->id, 3600, function () use ($group) { + return [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'id' => $group->url(), + 'inbox' => $group->permalink('/inbox'), + 'name' => $group->name, + 'outbox' => $group->permalink('/outbox'), + 'summary' => $group->description, + 'type' => 'Group', + 'attributedTo' => [ + 'type' => 'Person', + 'id' => $group->admin->permalink(), + ], + // 'endpoints' => [ + // 'sharedInbox' => config('app.url') . '/f/inbox' + // ], + 'preferredUsername' => 'gid_'.$group->id, + 'publicKey' => [ + 'id' => $group->permalink('#main-key'), + 'owner' => $group->permalink(), + 'publicKeyPem' => InstanceActor::first()->public_key, + ], + 'url' => $group->permalink(), + ]; - if($group->metadata && isset($group->metadata['header'])) { - $res['image'] = [ - 'type' => 'Image', - 'url' => $group->metadata['header']['url'] - ]; - } - ksort($res); - return $res; - }); - } + if ($group->metadata && isset($group->metadata['avatar'])) { + $res['icon'] = [ + 'type' => 'Image', + 'url' => $group->metadata['avatar']['url'], + ]; + } - public function getStatusObject(Request $request, $gid, $sid) - { - $group = Group::whereLocal(true)->whereActivitypub(true)->findOrFail($gid); - $gp = GroupPost::whereGroupId($gid)->findOrFail($sid); - $status = Status::findOrFail($gp->status_id); - // permission check + if ($group->metadata && isset($group->metadata['header'])) { + $res['image'] = [ + 'type' => 'Image', + 'url' => $group->metadata['header']['url'], + ]; + } + ksort($res); - $res = [ - '@context' => 'https://www.w3.org/ns/activitystreams', - 'id' => $gp->url(), + return $res; + }); + } - 'type' => 'Note', + public function getStatusObject(Request $request, $gid, $sid) + { + $group = Group::whereLocal(true)->whereActivitypub(true)->findOrFail($gid); + $gp = GroupPost::whereGroupId($gid)->findOrFail($sid); + $status = Status::findOrFail($gp->status_id); + // permission check + $content = $status->caption ? Autolink::create()->autolink($status->caption) : null; + $res = [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'id' => $gp->url(), - 'summary' => null, - 'content' => $status->rendered ?? $status->caption, - 'inReplyTo' => null, + 'type' => 'Note', - 'published' => $status->created_at->toAtomString(), - 'url' => $gp->url(), - 'attributedTo' => $status->profile->permalink(), - 'to' => [ - 'https://www.w3.org/ns/activitystreams#Public', - $group->permalink('/followers'), - ], - 'cc' => [], - 'sensitive' => (bool) $status->is_nsfw, - 'attachment' => MediaService::activitypub($status->id), - 'target' => [ - 'type' => 'Collection', - 'id' => $group->permalink('/wall'), - 'attributedTo' => $group->permalink() - ] - ]; - // ksort($res); - return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES); - } + 'summary' => null, + 'content' => $content, + 'inReplyTo' => null, + + 'published' => $status->created_at->toAtomString(), + 'url' => $gp->url(), + 'attributedTo' => $status->profile->permalink(), + 'to' => [ + 'https://www.w3.org/ns/activitystreams#Public', + $group->permalink('/followers'), + ], + 'cc' => [], + 'sensitive' => (bool) $status->is_nsfw, + 'attachment' => MediaService::activitypub($status->id), + 'target' => [ + 'type' => 'Collection', + 'id' => $group->permalink('/wall'), + 'attributedTo' => $group->permalink(), + ], + ]; + + // ksort($res); + return response()->json($res, 200, [], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + } } diff --git a/app/Http/Controllers/InternalApiController.php b/app/Http/Controllers/InternalApiController.php index 299c9ceb6..e2795d6fc 100644 --- a/app/Http/Controllers/InternalApiController.php +++ b/app/Http/Controllers/InternalApiController.php @@ -2,442 +2,424 @@ namespace App\Http\Controllers; -use Illuminate\Http\Request; -use App\{ - AccountInterstitial, - Bookmark, - DirectMessage, - DiscoverCategory, - Hashtag, - Follower, - Like, - Media, - MediaTag, - Notification, - Profile, - StatusHashtag, - Status, - User, - UserFilter, -}; -use Auth,Cache; -use Illuminate\Support\Facades\Redis; -use Carbon\Carbon; -use League\Fractal; -use App\Transformer\Api\{ - AccountTransformer, - StatusTransformer, - // StatusMediaContainerTransformer, -}; -use App\Util\Media\Filter; -use App\Jobs\StatusPipeline\NewStatusPipeline; +use App\AccountInterstitial; +use App\Bookmark; +use App\DirectMessage; +use App\DiscoverCategory; +use App\Follower; use App\Jobs\ModPipeline\HandleSpammerPipeline; -use League\Fractal\Serializer\ArraySerializer; -use League\Fractal\Pagination\IlluminatePaginatorAdapter; -use Illuminate\Validation\Rule; -use Illuminate\Support\Str; -use App\Services\MediaTagService; +use App\Profile; +use App\Services\BookmarkService; +use App\Services\DiscoverService; use App\Services\ModLogService; use App\Services\PublicTimelineService; -use App\Services\SnowflakeService; use App\Services\StatusService; use App\Services\UserFilterService; -use App\Services\DiscoverService; -use App\Services\BookmarkService; +use App\Status; // StatusMediaContainerTransformer, +use App\Transformer\Api\StatusTransformer; +use App\User; +use Auth; +use Cache; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Redis; +use Illuminate\Validation\Rule; +use League\Fractal; +use League\Fractal\Serializer\ArraySerializer; class InternalApiController extends Controller { - protected $fractal; + protected $fractal; - public function __construct() - { - $this->middleware('auth'); - $this->fractal = new Fractal\Manager(); - $this->fractal->setSerializer(new ArraySerializer()); - } + public function __construct() + { + $this->middleware('auth'); + $this->fractal = new Fractal\Manager; + $this->fractal->setSerializer(new ArraySerializer); + } - // deprecated v2 compose api - public function compose(Request $request) - { - return redirect('/'); - } + // deprecated v2 compose api + public function compose(Request $request) + { + return redirect('/'); + } - // deprecated - public function discover(Request $request) - { - return; - } + // deprecated + public function discover(Request $request) {} - public function discoverPosts(Request $request) - { - $pid = $request->user()->profile_id; - $filters = UserFilterService::filters($pid); - $forYou = DiscoverService::getForYou(); - $posts = $forYou->take(50)->map(function($post) { - return StatusService::get($post); - }) - ->filter(function($post) use($filters) { - return $post && - isset($post['account']) && - isset($post['account']['id']) && - !in_array($post['account']['id'], $filters); - }) - ->take(12) - ->values(); - return response()->json(compact('posts')); - } + public function discoverPosts(Request $request) + { + $pid = $request->user()->profile_id; + $filters = UserFilterService::filters($pid); + $forYou = DiscoverService::getForYou(); + $posts = $forYou->take(50)->map(function ($post) { + return StatusService::get($post); + }) + ->filter(function ($post) use ($filters) { + return $post && + isset($post['account']) && + isset($post['account']['id']) && + ! in_array($post['account']['id'], $filters); + }) + ->take(12) + ->values(); - public function directMessage(Request $request, $profileId, $threadId) - { - $profile = Auth::user()->profile; + return response()->json(compact('posts')); + } - if($profileId != $profile->id) { - abort(403); - } + public function directMessage(Request $request, $profileId, $threadId) + { + $profile = Auth::user()->profile; - $msg = DirectMessage::whereToId($profile->id) - ->orWhere('from_id',$profile->id) - ->findOrFail($threadId); + if ($profileId != $profile->id) { + abort(403); + } - $thread = DirectMessage::with('status')->whereIn('to_id', [$profile->id, $msg->from_id]) - ->whereIn('from_id', [$profile->id,$msg->from_id]) - ->orderBy('created_at', 'asc') - ->paginate(30); + $msg = DirectMessage::whereToId($profile->id) + ->orWhere('from_id', $profile->id) + ->findOrFail($threadId); - return response()->json(compact('msg', 'profile', 'thread'), 200, [], JSON_PRETTY_PRINT); - } + $thread = DirectMessage::with('status')->whereIn('to_id', [$profile->id, $msg->from_id]) + ->whereIn('from_id', [$profile->id, $msg->from_id]) + ->orderBy('created_at', 'asc') + ->paginate(30); - public function statusReplies(Request $request, int $id) - { - $this->validate($request, [ - 'limit' => 'nullable|int|min:1|max:6' - ]); - $parent = Status::whereScope('public')->findOrFail($id); - $limit = $request->input('limit') ?? 3; - $children = Status::whereInReplyToId($parent->id) - ->orderBy('created_at', 'desc') - ->take($limit) - ->get(); - $resource = new Fractal\Resource\Collection($children, new StatusTransformer()); - $res = $this->fractal->createData($resource)->toArray(); + return response()->json(compact('msg', 'profile', 'thread'), 200, [], JSON_PRETTY_PRINT); + } - return response()->json($res); - } + public function statusReplies(Request $request, int $id) + { + $this->validate($request, [ + 'limit' => 'nullable|int|min:1|max:6', + ]); + $parent = Status::whereScope('public')->findOrFail($id); + $limit = $request->input('limit') ?? 3; + $children = Status::whereInReplyToId($parent->id) + ->orderBy('created_at', 'desc') + ->take($limit) + ->get(); + $resource = new Fractal\Resource\Collection($children, new StatusTransformer); + $res = $this->fractal->createData($resource)->toArray(); - public function stories(Request $request) - { + return response()->json($res); + } - } + public function stories(Request $request) {} - public function discoverCategories(Request $request) - { - $categories = DiscoverCategory::whereActive(true)->orderBy('order')->take(10)->get(); - $res = $categories->map(function($item) { - return [ - 'name' => $item->name, - 'url' => $item->url(), - 'thumb' => $item->thumb() - ]; - }); - return response()->json($res); - } + public function discoverCategories(Request $request) + { + $categories = DiscoverCategory::whereActive(true)->orderBy('order')->take(10)->get(); + $res = $categories->map(function ($item) { + return [ + 'name' => $item->name, + 'url' => $item->url(), + 'thumb' => $item->thumb(), + ]; + }); - public function modAction(Request $request) - { - abort_unless(Auth::user()->is_admin, 400); - $this->validate($request, [ - 'action' => [ - 'required', - 'string', - Rule::in([ - 'addcw', - 'remcw', - 'unlist', - 'spammer' - ]) - ], - 'item_id' => 'required|integer|min:1', - 'item_type' => [ - 'required', - 'string', - Rule::in(['profile', 'status']) - ] - ]); + return response()->json($res); + } - $action = $request->input('action'); - $item_id = $request->input('item_id'); - $item_type = $request->input('item_type'); + public function modAction(Request $request) + { + abort_unless(Auth::user()->is_admin, 400); + $this->validate($request, [ + 'action' => [ + 'required', + 'string', + Rule::in([ + 'addcw', + 'remcw', + 'unlist', + 'spammer', + ]), + ], + 'item_id' => 'required|integer|min:1', + 'item_type' => [ + 'required', + 'string', + Rule::in(['profile', 'status']), + ], + ]); - $status = Status::findOrFail($item_id); - $author = User::whereProfileId($status->profile_id)->first(); - abort_if($author && $author->is_admin, 422, 'Cannot moderate administrator accounts'); + $action = $request->input('action'); + $item_id = $request->input('item_id'); + $item_type = $request->input('item_type'); - switch($action) { - case 'addcw': - $status->is_nsfw = true; - $status->save(); - ModLogService::boot() - ->user(Auth::user()) - ->objectUid($status->profile->user_id) - ->objectId($status->id) - ->objectType('App\Status::class') - ->action('admin.status.moderate') - ->metadata([ - 'action' => 'cw', - 'message' => 'Success!' - ]) - ->accessLevel('admin') - ->save(); + $status = Status::findOrFail($item_id); + $author = User::whereProfileId($status->profile_id)->first(); + abort_if($author && $author->is_admin, 422, 'Cannot moderate administrator accounts'); - if($status->uri == null) { - $media = $status->media; - $ai = new AccountInterstitial; - $ai->user_id = $status->profile->user_id; - $ai->type = 'post.cw'; - $ai->view = 'account.moderation.post.cw'; - $ai->item_type = 'App\Status'; - $ai->item_id = $status->id; - $ai->has_media = (bool) $media->count(); - $ai->blurhash = $media->count() ? $media->first()->blurhash : null; - $ai->meta = json_encode([ - 'caption' => $status->caption, - 'created_at' => $status->created_at, - 'type' => $status->type, - 'url' => $status->url(), - 'is_nsfw' => $status->is_nsfw, - 'scope' => $status->scope, - 'reblog' => $status->reblog_of_id, - 'likes_count' => $status->likes_count, - 'reblogs_count' => $status->reblogs_count, - ]); - $ai->save(); + switch ($action) { + case 'addcw': + $status->is_nsfw = true; + $status->save(); + ModLogService::boot() + ->user(Auth::user()) + ->objectUid($status->profile->user_id) + ->objectId($status->id) + ->objectType('App\Status::class') + ->action('admin.status.moderate') + ->metadata([ + 'action' => 'cw', + 'message' => 'Success!', + ]) + ->accessLevel('admin') + ->save(); - $u = $status->profile->user; - $u->has_interstitial = true; - $u->save(); - } - break; + if ($status->uri == null) { + $media = $status->media; + $ai = new AccountInterstitial; + $ai->user_id = $status->profile->user_id; + $ai->type = 'post.cw'; + $ai->view = 'account.moderation.post.cw'; + $ai->item_type = 'App\Status'; + $ai->item_id = $status->id; + $ai->has_media = (bool) $media->count(); + $ai->blurhash = $media->count() ? $media->first()->blurhash : null; + $ai->meta = json_encode([ + 'caption' => $status->caption, + 'created_at' => $status->created_at, + 'type' => $status->type, + 'url' => $status->url(), + 'is_nsfw' => $status->is_nsfw, + 'scope' => $status->scope, + 'reblog' => $status->reblog_of_id, + 'likes_count' => $status->likes_count, + 'reblogs_count' => $status->reblogs_count, + ]); + $ai->save(); - case 'remcw': - $status->is_nsfw = false; - $status->save(); - ModLogService::boot() - ->user(Auth::user()) - ->objectUid($status->profile->user_id) - ->objectId($status->id) - ->objectType('App\Status::class') - ->action('admin.status.moderate') - ->metadata([ - 'action' => 'remove_cw', - 'message' => 'Success!' - ]) - ->accessLevel('admin') - ->save(); - if($status->uri == null) { - $ai = AccountInterstitial::whereUserId($status->profile->user_id) - ->whereType('post.cw') - ->whereItemId($status->id) - ->whereItemType('App\Status') - ->first(); - $ai->delete(); - } - break; + $u = $status->profile->user; + $u->has_interstitial = true; + $u->save(); + } + break; - case 'unlist': - $status->scope = $status->visibility = 'unlisted'; - $status->save(); - PublicTimelineService::del($status->id); - ModLogService::boot() - ->user(Auth::user()) - ->objectUid($status->profile->user_id) - ->objectId($status->id) - ->objectType('App\Status::class') - ->action('admin.status.moderate') - ->metadata([ - 'action' => 'unlist', - 'message' => 'Success!' - ]) - ->accessLevel('admin') - ->save(); + case 'remcw': + $status->is_nsfw = false; + $status->save(); + ModLogService::boot() + ->user(Auth::user()) + ->objectUid($status->profile->user_id) + ->objectId($status->id) + ->objectType('App\Status::class') + ->action('admin.status.moderate') + ->metadata([ + 'action' => 'remove_cw', + 'message' => 'Success!', + ]) + ->accessLevel('admin') + ->save(); + if ($status->uri == null) { + $ai = AccountInterstitial::whereUserId($status->profile->user_id) + ->whereType('post.cw') + ->whereItemId($status->id) + ->whereItemType('App\Status') + ->first(); + $ai->delete(); + } + break; - if($status->uri == null) { - $media = $status->media; - $ai = new AccountInterstitial; - $ai->user_id = $status->profile->user_id; - $ai->type = 'post.unlist'; - $ai->view = 'account.moderation.post.unlist'; - $ai->item_type = 'App\Status'; - $ai->item_id = $status->id; - $ai->has_media = (bool) $media->count(); - $ai->blurhash = $media->count() ? $media->first()->blurhash : null; - $ai->meta = json_encode([ - 'caption' => $status->caption, - 'created_at' => $status->created_at, - 'type' => $status->type, - 'url' => $status->url(), - 'is_nsfw' => $status->is_nsfw, - 'scope' => $status->scope, - 'reblog' => $status->reblog_of_id, - 'likes_count' => $status->likes_count, - 'reblogs_count' => $status->reblogs_count, - ]); - $ai->save(); + case 'unlist': + $status->scope = $status->visibility = 'unlisted'; + $status->save(); + PublicTimelineService::del($status->id); + ModLogService::boot() + ->user(Auth::user()) + ->objectUid($status->profile->user_id) + ->objectId($status->id) + ->objectType('App\Status::class') + ->action('admin.status.moderate') + ->metadata([ + 'action' => 'unlist', + 'message' => 'Success!', + ]) + ->accessLevel('admin') + ->save(); - $u = $status->profile->user; - $u->has_interstitial = true; - $u->save(); - } - break; + if ($status->uri == null) { + $media = $status->media; + $ai = new AccountInterstitial; + $ai->user_id = $status->profile->user_id; + $ai->type = 'post.unlist'; + $ai->view = 'account.moderation.post.unlist'; + $ai->item_type = 'App\Status'; + $ai->item_id = $status->id; + $ai->has_media = (bool) $media->count(); + $ai->blurhash = $media->count() ? $media->first()->blurhash : null; + $ai->meta = json_encode([ + 'caption' => $status->caption, + 'created_at' => $status->created_at, + 'type' => $status->type, + 'url' => $status->url(), + 'is_nsfw' => $status->is_nsfw, + 'scope' => $status->scope, + 'reblog' => $status->reblog_of_id, + 'likes_count' => $status->likes_count, + 'reblogs_count' => $status->reblogs_count, + ]); + $ai->save(); - case 'spammer': - HandleSpammerPipeline::dispatch($status->profile); - ModLogService::boot() - ->user(Auth::user()) - ->objectUid($status->profile->user_id) - ->objectId($status->id) - ->objectType('App\User::class') - ->action('admin.status.moderate') - ->metadata([ - 'action' => 'spammer', - 'message' => 'Success!' - ]) - ->accessLevel('admin') - ->save(); - break; - } + $u = $status->profile->user; + $u->has_interstitial = true; + $u->save(); + } + break; - StatusService::del($status->id, true); - return ['msg' => 200]; - } + case 'spammer': + HandleSpammerPipeline::dispatch($status->profile); + ModLogService::boot() + ->user(Auth::user()) + ->objectUid($status->profile->user_id) + ->objectId($status->id) + ->objectType('App\User::class') + ->action('admin.status.moderate') + ->metadata([ + 'action' => 'spammer', + 'message' => 'Success!', + ]) + ->accessLevel('admin') + ->save(); + break; + } - public function composePost(Request $request) - { - abort(400, 'Endpoint deprecated'); - } + StatusService::del($status->id, true); - public function bookmarks(Request $request) - { - $pid = $request->user()->profile_id; - $res = Bookmark::whereProfileId($pid) - ->orderByDesc('created_at') - ->simplePaginate(10) - ->map(function($bookmark) use($pid) { - $status = StatusService::get($bookmark->status_id, false); - if(!$status) { - return false; - } - $status['bookmarked_at'] = str_replace('+00:00', 'Z', $bookmark->created_at->format(DATE_RFC3339_EXTENDED)); + return ['msg' => 200]; + } - if($status) { - BookmarkService::add($pid, $status['id']); - } - return $status; - }) - ->filter(function($bookmark) { - return $bookmark && isset($bookmark['id']); - }) - ->values(); + public function composePost(Request $request) + { + abort(400, 'Endpoint deprecated'); + } - return response()->json($res); - } + public function bookmarks(Request $request) + { + $pid = $request->user()->profile_id; + $res = Bookmark::whereProfileId($pid) + ->orderByDesc('created_at') + ->simplePaginate(10) + ->map(function ($bookmark) use ($pid) { + $status = StatusService::get($bookmark->status_id, false); + if (! $status) { + return false; + } + $status['bookmarked_at'] = str_replace('+00:00', 'Z', $bookmark->created_at->format(DATE_RFC3339_EXTENDED)); - public function accountStatuses(Request $request, $id) - { - $this->validate($request, [ - 'only_media' => 'nullable', - 'pinned' => 'nullable', - 'exclude_replies' => 'nullable', - 'max_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX, - 'since_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX, - 'min_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX, - 'limit' => 'nullable|integer|min:1|max:24' - ]); + if ($status) { + BookmarkService::add($pid, $status['id']); + } - $profile = Profile::whereNull('status')->findOrFail($id); + return $status; + }) + ->filter(function ($bookmark) { + return $bookmark && isset($bookmark['id']); + }) + ->values(); - $limit = $request->limit ?? 9; - $max_id = $request->max_id; - $min_id = $request->min_id; - $scope = $request->only_media == true ? - ['photo', 'photo:album', 'video', 'video:album'] : - ['photo', 'photo:album', 'video', 'video:album', 'share', 'reply']; + return response()->json($res); + } - if($profile->is_private) { - if(!Auth::check()) { - return response()->json([]); - } - $pid = Auth::user()->profile->id; - $following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function() use($pid) { - $following = Follower::whereProfileId($pid)->pluck('following_id'); - return $following->push($pid)->toArray(); - }); - $visibility = true == in_array($profile->id, $following) ? ['public', 'unlisted', 'private'] : []; - } else { - if(Auth::check()) { - $pid = Auth::user()->profile->id; - $following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function() use($pid) { - $following = Follower::whereProfileId($pid)->pluck('following_id'); - return $following->push($pid)->toArray(); - }); - $visibility = true == in_array($profile->id, $following) ? ['public', 'unlisted', 'private'] : ['public', 'unlisted']; - } else { - $visibility = ['public', 'unlisted']; - } - } + public function accountStatuses(Request $request, $id) + { + $this->validate($request, [ + 'only_media' => 'nullable', + 'pinned' => 'nullable', + 'exclude_replies' => 'nullable', + 'max_id' => 'nullable|integer|min:0|max:'.PHP_INT_MAX, + 'since_id' => 'nullable|integer|min:0|max:'.PHP_INT_MAX, + 'min_id' => 'nullable|integer|min:0|max:'.PHP_INT_MAX, + 'limit' => 'nullable|integer|min:1|max:24', + ]); - $dir = $min_id ? '>' : '<'; - $id = $min_id ?? $max_id; - $timeline = Status::select( - 'id', - 'uri', - 'caption', - 'rendered', - 'profile_id', - 'type', - 'in_reply_to_id', - 'reblog_of_id', - 'is_nsfw', - 'likes_count', - 'reblogs_count', - 'scope', - 'local', - 'created_at', - 'updated_at' - )->whereProfileId($profile->id) - ->whereIn('type', $scope) - ->where('id', $dir, $id) - ->whereIn('visibility', $visibility) - ->latest() - ->limit($limit) - ->get(); + $profile = Profile::whereNull('status')->findOrFail($id); - $resource = new Fractal\Resource\Collection($timeline, new StatusTransformer()); - $res = $this->fractal->createData($resource)->toArray(); + $limit = $request->limit ?? 9; + $max_id = $request->max_id; + $min_id = $request->min_id; + $scope = $request->only_media == true ? + ['photo', 'photo:album', 'video', 'video:album'] : + ['photo', 'photo:album', 'video', 'video:album', 'share', 'reply']; - return response()->json($res); - } + if ($profile->is_private) { + if (! Auth::check()) { + return response()->json([]); + } + $pid = Auth::user()->profile->id; + $following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function () use ($pid) { + $following = Follower::whereProfileId($pid)->pluck('following_id'); - public function remoteProfile(Request $request, $id) - { - return redirect('/i/web/profile/' . $id); - } + return $following->push($pid)->toArray(); + }); + $visibility = in_array($profile->id, $following) == true ? ['public', 'unlisted', 'private'] : []; + } else { + if (Auth::check()) { + $pid = Auth::user()->profile->id; + $following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function () use ($pid) { + $following = Follower::whereProfileId($pid)->pluck('following_id'); - public function remoteStatus(Request $request, $profileId, $statusId) - { - return redirect('/i/web/post/' . $statusId); - } + return $following->push($pid)->toArray(); + }); + $visibility = in_array($profile->id, $following) == true ? ['public', 'unlisted', 'private'] : ['public', 'unlisted']; + } else { + $visibility = ['public', 'unlisted']; + } + } - public function requestEmailVerification(Request $request) - { - $pid = $request->user()->profile_id; - $exists = Redis::sismember('email:manual', $pid); - return view('account.email.request_verification', compact('exists')); - } + $dir = $min_id ? '>' : '<'; + $id = $min_id ?? $max_id; + $timeline = Status::select( + 'id', + 'uri', + 'caption', + 'profile_id', + 'type', + 'in_reply_to_id', + 'reblog_of_id', + 'is_nsfw', + 'likes_count', + 'reblogs_count', + 'scope', + 'local', + 'created_at', + 'updated_at' + )->whereProfileId($profile->id) + ->whereIn('type', $scope) + ->where('id', $dir, $id) + ->whereIn('visibility', $visibility) + ->latest() + ->limit($limit) + ->get(); - public function requestEmailVerificationStore(Request $request) - { - $pid = $request->user()->profile_id; - Redis::sadd('email:manual', $pid); - return redirect('/i/verify-email')->with(['status' => 'Successfully sent manual verification request!']); - } + $resource = new Fractal\Resource\Collection($timeline, new StatusTransformer); + $res = $this->fractal->createData($resource)->toArray(); + + return response()->json($res); + } + + public function remoteProfile(Request $request, $id) + { + return redirect('/i/web/profile/'.$id); + } + + public function remoteStatus(Request $request, $profileId, $statusId) + { + return redirect('/i/web/post/'.$statusId); + } + + public function requestEmailVerification(Request $request) + { + $pid = $request->user()->profile_id; + $exists = Redis::sismember('email:manual', $pid); + + return view('account.email.request_verification', compact('exists')); + } + + public function requestEmailVerificationStore(Request $request) + { + $pid = $request->user()->profile_id; + Redis::sadd('email:manual', $pid); + + return redirect('/i/verify-email')->with(['status' => 'Successfully sent manual verification request!']); + } } diff --git a/app/Http/Controllers/MicroController.php b/app/Http/Controllers/MicroController.php index 420083f0f..62e0ae969 100644 --- a/app/Http/Controllers/MicroController.php +++ b/app/Http/Controllers/MicroController.php @@ -2,66 +2,65 @@ namespace App\Http\Controllers; +use App\Status; +use Auth; +use DB; use Illuminate\Http\Request; -use App\{ - Profile, - Status, -}; -use Auth, DB, Purify; use Illuminate\Validation\Rule; class MicroController extends Controller { - public function __construct() - { - $this->middleware('auth'); - } + public function __construct() + { + $this->middleware('auth'); + } - public function composeText(Request $request) - { - $this->validate($request, [ - 'type' => [ - 'required', - 'string', - Rule::in(['text']) - ], - 'title' => 'nullable|string|max:140', - 'content' => 'required|string|max:500', - 'visibility' => [ - 'required', - 'string', - Rule::in([ - 'public', - 'unlisted', - 'private', - 'draft' - ]) - ] - ]); - $profile = Auth::user()->profile; - $title = $request->input('title'); - $content = $request->input('content'); - $visibility = $request->input('visibility'); + public function composeText(Request $request) + { + $this->validate($request, [ + 'type' => [ + 'required', + 'string', + Rule::in(['text']), + ], + 'title' => 'nullable|string|max:140', + 'content' => 'required|string|max:500', + 'visibility' => [ + 'required', + 'string', + Rule::in([ + 'public', + 'unlisted', + 'private', + 'draft', + ]), + ], + ]); + $profile = Auth::user()->profile; + $title = $request->input('title'); + $content = $request->input('content'); + $visibility = $request->input('visibility'); - $status = DB::transaction(function() use($profile, $content, $visibility, $title) { - $status = new Status; - $status->type = 'text'; - $status->profile_id = $profile->id; - $status->caption = strip_tags($content); - $status->rendered = Purify::clean($content); - $status->is_nsfw = false; + $status = DB::transaction(function () use ($profile, $content, $visibility, $title) { + $status = new Status; + $status->type = 'text'; + $status->profile_id = $profile->id; + $status->caption = strip_tags($content); + $status->is_nsfw = false; - // TODO: remove deprecated visibility in favor of scope - $status->visibility = $visibility; - $status->scope = $visibility; - $status->entities = json_encode(['title'=>$title]); - $status->save(); - return $status; - }); + // TODO: remove deprecated visibility in favor of scope + $status->visibility = $visibility; + $status->scope = $visibility; + $status->entities = json_encode(['title' => $title]); + $status->save(); - $fractal = new \League\Fractal\Manager(); - $fractal->setSerializer(new \League\Fractal\Serializer\ArraySerializer()); - $s = new \League\Fractal\Resource\Item($status, new \App\Transformer\Api\StatusTransformer()); - return $fractal->createData($s)->toArray(); - } + return $status; + }); + + $fractal = new \League\Fractal\Manager; + $fractal->setSerializer(new \League\Fractal\Serializer\ArraySerializer); + $s = new \League\Fractal\Resource\Item($status, new \App\Transformer\Api\StatusTransformer); + + return $fractal->createData($s)->toArray(); + } } diff --git a/app/Http/Controllers/SearchController.php b/app/Http/Controllers/SearchController.php index 9388d3abd..8e4296e5b 100644 --- a/app/Http/Controllers/SearchController.php +++ b/app/Http/Controllers/SearchController.php @@ -8,6 +8,7 @@ use App\Profile; use App\Services\WebfingerService; use App\Status; use App\Util\ActivityPub\Helpers; +use App\Util\Lexer\Autolink; use Auth; use Illuminate\Http\Request; use Illuminate\Support\Facades\Cache; @@ -320,17 +321,21 @@ class SearchController extends Controller if (Status::whereUri($tag)->whereLocal(false)->exists()) { $item = Status::whereUri($tag)->first(); + if (! $item) { + return; + } $media = $item->firstMedia(); $url = null; if ($media) { $url = $media->remote_url; } + $content = $item->caption ? Autolink::create()->autolink($item->caption) : null; $this->tokens['posts'] = [[ 'count' => 0, 'url' => "/i/web/post/_/$item->profile_id/$item->id", 'type' => 'status', 'username' => $item->profile->username, - 'caption' => $item->rendered ?? $item->caption, + 'caption' => $content, 'thumb' => $url, 'timestamp' => $item->created_at->diffForHumans(), ]]; @@ -340,17 +345,21 @@ class SearchController extends Controller if (isset($remote['type']) && $remote['type'] == 'Note') { $item = Helpers::statusFetch($tag); + if (! $item) { + return; + } $media = $item->firstMedia(); $url = null; if ($media) { $url = $media->remote_url; } + $content = $item->caption ? Autolink::create()->autolink($item->caption) : null; $this->tokens['posts'] = [[ 'count' => 0, 'url' => "/i/web/post/_/$item->profile_id/$item->id", 'type' => 'status', 'username' => $item->profile->username, - 'caption' => $item->rendered ?? $item->caption, + 'caption' => $content, 'thumb' => $url, 'timestamp' => $item->created_at->diffForHumans(), ]]; diff --git a/app/Http/Controllers/Stories/StoryApiV1Controller.php b/app/Http/Controllers/Stories/StoryApiV1Controller.php index 5d0a15160..48599fc3a 100644 --- a/app/Http/Controllers/Stories/StoryApiV1Controller.php +++ b/app/Http/Controllers/Stories/StoryApiV1Controller.php @@ -281,7 +281,7 @@ class StoryApiV1Controller extends Controller $photo = $request->file('file'); $path = $this->storeMedia($photo, $user); - $story = new Story(); + $story = new Story; $story->duration = $request->input('duration', 3); $story->profile_id = $user->profile_id; $story->type = Str::endsWith($photo->getMimeType(), 'mp4') ? 'video' : 'photo'; @@ -418,7 +418,6 @@ class StoryApiV1Controller extends Controller $status->type = 'story:reply'; $status->profile_id = $pid; $status->caption = $text; - $status->rendered = $text; $status->scope = 'direct'; $status->visibility = 'direct'; $status->in_reply_to_profile_id = $story->profile_id; diff --git a/app/Http/Controllers/StoryComposeController.php b/app/Http/Controllers/StoryComposeController.php index c8b0599a6..e02e2d219 100644 --- a/app/Http/Controllers/StoryComposeController.php +++ b/app/Http/Controllers/StoryComposeController.php @@ -54,7 +54,7 @@ class StoryComposeController extends Controller $photo = $request->file('file'); $path = $this->storePhoto($photo, $user); - $story = new Story(); + $story = new Story; $story->duration = 3; $story->profile_id = $user->profile_id; $story->type = Str::endsWith($photo->getMimeType(), 'mp4') ? 'video' : 'photo'; @@ -403,7 +403,6 @@ class StoryComposeController extends Controller $status->profile_id = $pid; $status->type = 'story:reaction'; $status->caption = $text; - $status->rendered = $text; $status->scope = 'direct'; $status->visibility = 'direct'; $status->in_reply_to_profile_id = $story->profile_id; @@ -477,7 +476,6 @@ class StoryComposeController extends Controller $status->type = 'story:reply'; $status->profile_id = $pid; $status->caption = $text; - $status->rendered = $text; $status->scope = 'direct'; $status->visibility = 'direct'; $status->in_reply_to_profile_id = $story->profile_id; diff --git a/app/Jobs/GroupPipeline/NewStatusPipeline.php b/app/Jobs/GroupPipeline/NewStatusPipeline.php index 4d8eeca5c..d791d81a4 100644 --- a/app/Jobs/GroupPipeline/NewStatusPipeline.php +++ b/app/Jobs/GroupPipeline/NewStatusPipeline.php @@ -2,129 +2,122 @@ namespace App\Jobs\GroupPipeline; -use App\Notification; use App\Hashtag; use App\Mention; -use App\Profile; -use App\Status; -use App\StatusHashtag; -use App\Models\GroupPostHashtag; use App\Models\GroupPost; -use Cache; +use App\Models\GroupPostHashtag; +use App\Profile; +use App\Services\StatusService; +use App\Status; +use App\Util\Lexer\Autolink; +use App\Util\Lexer\Extractor; use DB; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; -use Illuminate\Support\Facades\Redis; -use App\Services\MediaStorageService; -use App\Services\NotificationService; -use App\Services\StatusService; -use App\Util\Lexer\Autolink; -use App\Util\Lexer\Extractor; class NewStatusPipeline implements ShouldQueue { - use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; + use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - protected $status; - protected $gp; - protected $tags; - protected $mentions; + protected $status; - public function __construct(Status $status, GroupPost $gp) - { - $this->status = $status; - $this->gp = $gp; - } + protected $gp; - public function handle() - { - $status = $this->status; + protected $tags; - $autolink = Autolink::create() - ->setAutolinkActiveUsersOnly(true) - ->setBaseHashPath("/groups/{$status->group_id}/topics/") - ->setBaseUserPath("/groups/{$status->group_id}/username/") - ->autolink($status->caption); + protected $mentions; + + public function __construct(Status $status, GroupPost $gp) + { + $this->status = $status; + $this->gp = $gp; + } + + public function handle() + { + $status = $this->status; + + $autolink = Autolink::create() + ->setAutolinkActiveUsersOnly(true) + ->setBaseHashPath("/groups/{$status->group_id}/topics/") + ->setBaseUserPath("/groups/{$status->group_id}/username/") + ->autolink($status->caption); $entities = Extractor::create()->extract($status->caption); + $status->entities = null; + $status->save(); - $autolink = str_replace('/discover/tags/', '/groups/' . $status->group_id . '/topics/', $autolink); + $this->tags = array_unique($entities['hashtags']); + $this->mentions = array_unique($entities['mentions']); - $status->rendered = nl2br($autolink); - $status->entities = null; - $status->save(); + if (count($this->tags)) { + $this->storeHashtags(); + } - $this->tags = array_unique($entities['hashtags']); - $this->mentions = array_unique($entities['mentions']); + if (count($this->mentions)) { + $this->storeMentions($this->mentions); + } + } - if(count($this->tags)) { - $this->storeHashtags(); - } + protected function storeHashtags() + { + $tags = $this->tags; + $status = $this->status; + $gp = $this->gp; - if(count($this->mentions)) { - $this->storeMentions($this->mentions); - } - } + foreach ($tags as $tag) { + if (mb_strlen($tag) > 124) { + continue; + } - protected function storeHashtags() - { - $tags = $this->tags; - $status = $this->status; - $gp = $this->gp; + DB::transaction(function () use ($status, $tag, $gp) { + $slug = str_slug($tag, '-', false); + $hashtag = Hashtag::firstOrCreate( + ['name' => $tag, 'slug' => $slug] + ); + GroupPostHashtag::firstOrCreate( + [ + 'group_id' => $status->group_id, + 'group_post_id' => $gp->id, + 'status_id' => $status->id, + 'hashtag_id' => $hashtag->id, + 'profile_id' => $status->profile_id, + ] + ); - foreach ($tags as $tag) { - if(mb_strlen($tag) > 124) { - continue; - } + }); + } - DB::transaction(function () use ($status, $tag, $gp) { - $slug = str_slug($tag, '-', false); - $hashtag = Hashtag::firstOrCreate( - ['name' => $tag, 'slug' => $slug] - ); - GroupPostHashtag::firstOrCreate( - [ - 'group_id' => $status->group_id, - 'group_post_id' => $gp->id, - 'status_id' => $status->id, - 'hashtag_id' => $hashtag->id, - 'profile_id' => $status->profile_id, - ] - ); + if (count($this->mentions)) { + $this->storeMentions(); + } + StatusService::del($status->id); + } - }); - } + protected function storeMentions() + { + $mentions = $this->mentions; + $status = $this->status; - if(count($this->mentions)) { - $this->storeMentions(); - } - StatusService::del($status->id); - } + foreach ($mentions as $mention) { + $mentioned = Profile::whereUsername($mention)->first(); - protected function storeMentions() - { - $mentions = $this->mentions; - $status = $this->status; + if (empty($mentioned) || ! isset($mentioned->id)) { + continue; + } - foreach ($mentions as $mention) { - $mentioned = Profile::whereUsername($mention)->first(); + DB::transaction(function () use ($status, $mentioned) { + $m = new Mention; + $m->status_id = $status->id; + $m->profile_id = $mentioned->id; + $m->save(); - if (empty($mentioned) || !isset($mentioned->id)) { - continue; - } - - DB::transaction(function () use ($status, $mentioned) { - $m = new Mention(); - $m->status_id = $status->id; - $m->profile_id = $mentioned->id; - $m->save(); - - MentionPipeline::dispatch($status, $m); - }); - } - StatusService::del($status->id); - } + MentionPipeline::dispatch($status, $m); + }); + } + StatusService::del($status->id); + } } diff --git a/app/Jobs/StatusPipeline/StatusEntityLexer.php b/app/Jobs/StatusPipeline/StatusEntityLexer.php index 4d19c7d8a..8fe767417 100644 --- a/app/Jobs/StatusPipeline/StatusEntityLexer.php +++ b/app/Jobs/StatusPipeline/StatusEntityLexer.php @@ -91,11 +91,6 @@ class StatusEntityLexer implements ShouldQueue public function storeEntities() { $this->storeHashtags(); - DB::transaction(function () { - $status = $this->status; - $status->rendered = nl2br($this->autolink); - $status->save(); - }); } public function storeHashtags() @@ -146,7 +141,7 @@ class StatusEntityLexer implements ShouldQueue } DB::transaction(function () use ($status, $mentioned) { - $m = new Mention(); + $m = new Mention; $m->status_id = $status->id; $m->profile_id = $mentioned->id; $m->save(); diff --git a/app/Jobs/StatusPipeline/StatusRemoteUpdatePipeline.php b/app/Jobs/StatusPipeline/StatusRemoteUpdatePipeline.php index 7ef7a3366..b216c0531 100644 --- a/app/Jobs/StatusPipeline/StatusRemoteUpdatePipeline.php +++ b/app/Jobs/StatusPipeline/StatusRemoteUpdatePipeline.php @@ -120,8 +120,7 @@ class StatusRemoteUpdatePipeline implements ShouldQueue protected function updateImmediateAttributes($status, $activity) { if (isset($activity['content'])) { - $status->caption = strip_tags($activity['content']); - $status->rendered = Purify::clean($activity['content']); + $status->caption = strip_tags(Purify::clean($activity['content'])); } if (isset($activity['sensitive'])) { diff --git a/app/Services/Status/UpdateStatusService.php b/app/Services/Status/UpdateStatusService.php index d0a69c451..7ef4d440c 100644 --- a/app/Services/Status/UpdateStatusService.php +++ b/app/Services/Status/UpdateStatusService.php @@ -3,135 +3,133 @@ namespace App\Services\Status; use App\Media; -use App\ModLog; -use App\Status; use App\Models\StatusEdit; -use Purify; -use App\Util\Lexer\Autolink; +use App\ModLog; use App\Services\MediaService; use App\Services\MediaStorageService; use App\Services\StatusService; +use App\Status; +use Purify; class UpdateStatusService { - public static function call(Status $status, $attributes) - { - self::createPreviousEdit($status); - self::updateMediaAttachements($status, $attributes); - self::handleImmediateAttributes($status, $attributes); - self::createEdit($status, $attributes); + public static function call(Status $status, $attributes) + { + self::createPreviousEdit($status); + self::updateMediaAttachements($status, $attributes); + self::handleImmediateAttributes($status, $attributes); + self::createEdit($status, $attributes); - return StatusService::get($status->id); - } + return StatusService::get($status->id); + } - public static function updateMediaAttachements(Status $status, $attributes) - { - $count = $status->media()->count(); - if($count === 0 || $count === 1) { - return; - } + public static function updateMediaAttachements(Status $status, $attributes) + { + $count = $status->media()->count(); + if ($count === 0 || $count === 1) { + return; + } - $oids = $status->media()->orderBy('order')->pluck('id')->map(function($m) { return (string) $m; }); - $nids = collect($attributes['media_ids']); + $oids = $status->media()->orderBy('order')->pluck('id')->map(function ($m) { + return (string) $m; + }); + $nids = collect($attributes['media_ids']); - if($oids->toArray() === $nids->toArray()) { - return; - } + if ($oids->toArray() === $nids->toArray()) { + return; + } - foreach($oids->diff($nids)->values()->toArray() as $mid) { - $media = Media::find($mid); - if(!$media) { - continue; - } - $media->status_id = null; - $media->save(); - MediaStorageService::delete($media, true); - } + foreach ($oids->diff($nids)->values()->toArray() as $mid) { + $media = Media::find($mid); + if (! $media) { + continue; + } + $media->status_id = null; + $media->save(); + MediaStorageService::delete($media, true); + } - $nids->each(function($nid, $idx) { - $media = Media::find($nid); - if(!$media) { - return; - } - $media->order = $idx; - $media->save(); - }); - MediaService::del($status->id); - } + $nids->each(function ($nid, $idx) { + $media = Media::find($nid); + if (! $media) { + return; + } + $media->order = $idx; + $media->save(); + }); + MediaService::del($status->id); + } - public static function handleImmediateAttributes(Status $status, $attributes) - { - if(isset($attributes['status'])) { - $cleaned = Purify::clean($attributes['status']); - $status->caption = $cleaned; - $status->rendered = nl2br(Autolink::create()->autolink($cleaned)); - } else { - $status->caption = null; - $status->rendered = null; - } - if(isset($attributes['sensitive'])) { - if($status->is_nsfw != (bool) $attributes['sensitive'] && - (bool) $attributes['sensitive'] == false) - { - $exists = ModLog::whereObjectType('App\Status::class') - ->whereObjectId($status->id) - ->whereAction('admin.status.moderate') - ->exists(); - if(!$exists) { - $status->is_nsfw = (bool) $attributes['sensitive']; - } - } else { - $status->is_nsfw = (bool) $attributes['sensitive']; - } - } - if(isset($attributes['spoiler_text'])) { - $status->cw_summary = Purify::clean($attributes['spoiler_text']); - } else { - $status->cw_summary = null; - } - if(isset($attributes['location'])) { - if (isset($attributes['location']['id'])) { - $status->place_id = $attributes['location']['id']; - } else { - $status->place_id = null; - } - } - if($status->cw_summary && !$status->is_nsfw) { - $status->cw_summary = null; - } - $status->edited_at = now(); - $status->save(); - StatusService::del($status->id); - } + public static function handleImmediateAttributes(Status $status, $attributes) + { + if (isset($attributes['status'])) { + $cleaned = Purify::clean($attributes['status']); + $status->caption = $cleaned; + } else { + $status->caption = null; + } + if (isset($attributes['sensitive'])) { + if ($status->is_nsfw != (bool) $attributes['sensitive'] && + (bool) $attributes['sensitive'] == false) { + $exists = ModLog::whereObjectType('App\Status::class') + ->whereObjectId($status->id) + ->whereAction('admin.status.moderate') + ->exists(); + if (! $exists) { + $status->is_nsfw = (bool) $attributes['sensitive']; + } + } else { + $status->is_nsfw = (bool) $attributes['sensitive']; + } + } + if (isset($attributes['spoiler_text'])) { + $status->cw_summary = Purify::clean($attributes['spoiler_text']); + } else { + $status->cw_summary = null; + } + if (isset($attributes['location'])) { + if (isset($attributes['location']['id'])) { + $status->place_id = $attributes['location']['id']; + } else { + $status->place_id = null; + } + } + if ($status->cw_summary && ! $status->is_nsfw) { + $status->cw_summary = null; + } + $status->edited_at = now(); + $status->save(); + StatusService::del($status->id); + } - public static function createPreviousEdit(Status $status) - { - if(!$status->edits()->count()) { - StatusEdit::create([ - 'status_id' => $status->id, - 'profile_id' => $status->profile_id, - 'caption' => $status->caption, - 'spoiler_text' => $status->cw_summary, - 'is_nsfw' => $status->is_nsfw, - 'ordered_media_attachment_ids' => $status->media()->orderBy('order')->pluck('id')->toArray(), - 'created_at' => $status->created_at - ]); - } - } + public static function createPreviousEdit(Status $status) + { + if (! $status->edits()->count()) { + StatusEdit::create([ + 'status_id' => $status->id, + 'profile_id' => $status->profile_id, + 'caption' => $status->caption, + 'spoiler_text' => $status->cw_summary, + 'is_nsfw' => $status->is_nsfw, + 'ordered_media_attachment_ids' => $status->media()->orderBy('order')->pluck('id')->toArray(), + 'created_at' => $status->created_at, + ]); + } + } - public static function createEdit(Status $status, $attributes) - { - $cleaned = isset($attributes['status']) ? Purify::clean($attributes['status']) : null; - $spoiler_text = isset($attributes['spoiler_text']) ? Purify::clean($attributes['spoiler_text']) : null; - $sensitive = isset($attributes['sensitive']) ? $attributes['sensitive'] : null; - $mids = $status->media()->count() ? $status->media()->orderBy('order')->pluck('id')->toArray() : null; - StatusEdit::create([ - 'status_id' => $status->id, - 'profile_id' => $status->profile_id, - 'caption' => $cleaned, - 'spoiler_text' => $spoiler_text, - 'is_nsfw' => $sensitive, - 'ordered_media_attachment_ids' => $mids - ]); - } + public static function createEdit(Status $status, $attributes) + { + $cleaned = isset($attributes['status']) ? Purify::clean($attributes['status']) : null; + $spoiler_text = isset($attributes['spoiler_text']) ? Purify::clean($attributes['spoiler_text']) : null; + $sensitive = isset($attributes['sensitive']) ? $attributes['sensitive'] : null; + $mids = $status->media()->count() ? $status->media()->orderBy('order')->pluck('id')->toArray() : null; + StatusEdit::create([ + 'status_id' => $status->id, + 'profile_id' => $status->profile_id, + 'caption' => $cleaned, + 'spoiler_text' => $spoiler_text, + 'is_nsfw' => $sensitive, + 'ordered_media_attachment_ids' => $mids, + ]); + } } diff --git a/app/Util/ActivityPub/Helpers.php b/app/Util/ActivityPub/Helpers.php index 93cab7754..bbc64dc26 100644 --- a/app/Util/ActivityPub/Helpers.php +++ b/app/Util/ActivityPub/Helpers.php @@ -694,8 +694,7 @@ class Helpers $status->url = isset($res['url']) ? $res['url'] : $url; $status->uri = isset($res['url']) ? $res['url'] : $url; $status->object_url = $id; - $status->caption = strip_tags($res['content']); - $status->rendered = Purify::clean($res['content']); + $status->caption = strip_tags(Purify::clean($res['content'])); $status->created_at = Carbon::parse($ts)->tz('UTC'); $status->in_reply_to_id = null; $status->local = false; diff --git a/app/Util/ActivityPub/Inbox.php b/app/Util/ActivityPub/Inbox.php index 8cca59cfc..e98b48c49 100644 --- a/app/Util/ActivityPub/Inbox.php +++ b/app/Util/ActivityPub/Inbox.php @@ -438,7 +438,6 @@ class Inbox $status = new Status; $status->profile_id = $actor->id; $status->caption = $msgText; - $status->rendered = $msg; $status->visibility = 'direct'; $status->scope = 'direct'; $status->url = $activity['id']; @@ -1081,7 +1080,6 @@ class Inbox $status->uri = $url; $status->object_url = $url; $status->caption = $text; - $status->rendered = $text; $status->scope = 'direct'; $status->visibility = 'direct'; $status->in_reply_to_profile_id = $story->profile_id; @@ -1199,7 +1197,6 @@ class Inbox $status->profile_id = $actorProfile->id; $status->type = 'story:reply'; $status->caption = $text; - $status->rendered = $text; $status->url = $url; $status->uri = $url; $status->object_url = $url;