diff --git a/CHANGELOG.md b/CHANGELOG.md index fbbad8716..d40b7478c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,8 @@ - Add Password change email notification ([de1cca4f](https://github.com/pixelfed/pixelfed/commit/de1cca4f)) - Add shared inbox ([4733ca9f](https://github.com/pixelfed/pixelfed/commit/4733ca9f)) - Add federated photo filters ([0a5a0e86](https://github.com/pixelfed/pixelfed/commit/0a5a0e86)) +- Add AccountInterstitial model and controller ([8766ccfe](https://github.com/pixelfed/pixelfed/commit/8766ccfe)) +- Add Blurhash encoder ([fad102bf](https://github.com/pixelfed/pixelfed/commit/fad102bf)) ### Updated - Updated PostComponent, fix remote urls ([42716ccc](https://github.com/pixelfed/pixelfed/commit/42716ccc)) @@ -106,6 +108,20 @@ - Updated federation config, make sharedInbox enabled by default. ([6e3522c0](https://github.com/pixelfed/pixelfed/commit/6e3522c0)) - Updated PostComponent, change timestamp format. ([e51665f6](https://github.com/pixelfed/pixelfed/commit/e51665f6)) - Updated PostComponent, use proper username context for reply mentions. Fixes ([#2421](https://github.com/pixelfed/pixelfed/issues/2421)). ([dac06088](https://github.com/pixelfed/pixelfed/commit/dac06088)) +- Updated Navbar, added profile avatar. ([19abf1b4](https://github.com/pixelfed/pixelfed/commit/19abf1b4)) +- Updated package.json, add blurhash. ([cc1b081a](https://github.com/pixelfed/pixelfed/commit/cc1b081a)) +- Updated Status model, fix thumb nsfw caching. ([327ef138](https://github.com/pixelfed/pixelfed/commit/327ef138)) +- Updated User model, add interstitial relation. ([bd321a72](https://github.com/pixelfed/pixelfed/commit/bd321a72)) +- Updated StatusStatelessTransformer, add missing attributes. ([4d22426d](https://github.com/pixelfed/pixelfed/commit/4d22426d)) +- Updated media pipeline, add blurhash support. ([473e0495](https://github.com/pixelfed/pixelfed/commit/473e0495)) +- Updated DeleteAccountPipeline, add AccountInterstitial and DirectMessage purging. ([b3078f27](https://github.com/pixelfed/pixelfed/commit/b3078f27)) +- Updated ComposeModal.vue component, reuse sharedData. ([e28d022f](https://github.com/pixelfed/pixelfed/commit/e28d022f)) +- Updated ApiController, return status object after deletion. ([0718711d](https://github.com/pixelfed/pixelfed/commit/0718711d)) +- Updated InternalApiController, add interstitial logic. ([20681bcf](https://github.com/pixelfed/pixelfed/commit/20681bcf)) +- Updated PublicApiController, improve stateless object caching. ([342e7a50](https://github.com/pixelfed/pixelfed/commit/342e7a50)) +- Updated StatusController, add interstitial logic. ([003caf7e](https://github.com/pixelfed/pixelfed/commit/003caf7e)) +- Updated middleware, add AccountInterstitial support. ([19d6e7df](https://github.com/pixelfed/pixelfed/commit/19d6e7df)) +- Updated BaseApiController, add favourites method. ([76353ca9](https://github.com/pixelfed/pixelfed/commit/76353ca9)) ## [v0.10.9 (2020-04-17)](https://github.com/pixelfed/pixelfed/compare/v0.10.8...v0.10.9) ### Added diff --git a/app/AccountInterstitial.php b/app/AccountInterstitial.php new file mode 100644 index 000000000..d2626ab58 --- /dev/null +++ b/app/AccountInterstitial.php @@ -0,0 +1,30 @@ +belongsTo(User::class); + } + + public function status() + { + if($this->item_type != 'App\Status') { + return; + } + return $this->hasOne(Status::class, 'id', 'item_id'); + } +} diff --git a/app/Http/Controllers/AccountInterstitialController.php b/app/Http/Controllers/AccountInterstitialController.php new file mode 100644 index 000000000..96c102e00 --- /dev/null +++ b/app/Http/Controllers/AccountInterstitialController.php @@ -0,0 +1,76 @@ +middleware('auth'); + } + + public function get(Request $request) + { + $interstitial = $request->user() + ->interstitials() + ->whereNull('read_at') + ->first(); + if(!$interstitial) { + $user = $request->user(); + $user->has_interstitial = false; + $user->save(); + return redirect('/'); + } + $meta = json_decode($interstitial->meta); + $view = $interstitial->view; + return view($view, compact('interstitial', 'meta')); + } + + public function read(Request $request) + { + $this->validate($request, [ + 'id' => 'required', + 'type' => 'required|in:post.cw,post.removed,post.unlist', + 'action' => 'required|in:appeal,confirm', + 'appeal_message' => 'nullable|max:500' + ]); + + $redirect = '/'; + + $id = decrypt($request->input('id')); + $action = $request->input('action'); + $user = $request->user(); + + $ai = AccountInterstitial::whereUserId($user->id) + ->whereType($request->input('type')) + ->findOrFail($id); + + if($action == 'appeal') { + $ai->appeal_requested_at = now(); + $ai->appeal_message = $request->input('appeal_message'); + } + + $ai->read_at = now(); + $ai->save(); + + $more = AccountInterstitial::whereUserId($user->id) + ->whereNull('read_at') + ->exists(); + + if(!$more) { + $user->has_interstitial = false; + $user->save(); + } + + if(in_array($ai->type, ['post.cw', 'post.unlist'])) { + $redirect = Status::findOrFail($ai->item_id)->url(); + } + + return redirect($redirect); + } +} diff --git a/app/Http/Controllers/AdminController.php b/app/Http/Controllers/AdminController.php index f6b693430..f044ec9fa 100644 --- a/app/Http/Controllers/AdminController.php +++ b/app/Http/Controllers/AdminController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers; use App\{ + AccountInterstitial, Contact, Hashtag, Newsroom, @@ -85,6 +86,67 @@ class AdminController extends Controller return view('admin.reports.show', compact('report')); } + public function appeals(Request $request) + { + $appeals = AccountInterstitial::whereNotNull('appeal_requested_at') + ->whereNull('appeal_handled_at') + ->latest() + ->paginate(6); + return view('admin.reports.appeals', compact('appeals')); + } + + public function showAppeal(Request $request, $id) + { + $appeal = AccountInterstitial::whereNotNull('appeal_requested_at') + ->whereNull('appeal_handled_at') + ->findOrFail($id); + $meta = json_decode($appeal->meta); + return view('admin.reports.show_appeal', compact('appeal', 'meta')); + } + + public function updateAppeal(Request $request, $id) + { + $this->validate($request, [ + 'action' => 'required|in:dismiss,approve' + ]); + + $action = $request->input('action'); + $appeal = AccountInterstitial::whereNotNull('appeal_requested_at') + ->whereNull('appeal_handled_at') + ->findOrFail($id); + + if($action == 'dismiss') { + $appeal->appeal_handled_at = now(); + $appeal->save(); + + return redirect('/i/admin/reports/appeals'); + } + + switch ($appeal->type) { + case 'post.cw': + $status = $appeal->status; + $status->is_nsfw = false; + $status->save(); + break; + + case 'post.unlist': + $status = $appeal->status; + $status->scope = 'public'; + $status->visibility = 'public'; + $status->save(); + break; + + default: + # code... + break; + } + + $appeal->appeal_handled_at = now(); + $appeal->save(); + + return redirect('/i/admin/reports/appeals'); + } + public function profiles(Request $request) { $this->validate($request, [ diff --git a/app/Http/Controllers/Api/ApiV1Controller.php b/app/Http/Controllers/Api/ApiV1Controller.php index 7bf0646e2..0243939b7 100644 --- a/app/Http/Controllers/Api/ApiV1Controller.php +++ b/app/Http/Controllers/Api/ApiV1Controller.php @@ -1761,6 +1761,7 @@ class ApiV1Controller extends Controller NewStatusPipeline::dispatch($status); Cache::forget('user:account:id:'.$user->id); + Cache::forget('_api:statuses:recent_9:'.$user->profile_id); Cache::forget('profile:status_count:'.$user->profile_id); Cache::forget($user->storageUsedKey()); @@ -1783,10 +1784,15 @@ class ApiV1Controller extends Controller $status = Status::whereProfileId($request->user()->profile->id) ->findOrFail($id); + $resource = new Fractal\Resource\Item($status, new StatusTransformer()); + Cache::forget('profile:status_count:'.$status->profile_id); StatusDelete::dispatch($status); - return response()->json(['Status successfully deleted.']); + $res = $this->fractal->createData($resource)->toArray(); + $res['text'] = $res['content']; + unset($res['content']); + return response()->json($res); } /** diff --git a/app/Http/Controllers/Api/BaseApiController.php b/app/Http/Controllers/Api/BaseApiController.php index 6c14d07a2..6a4497587 100644 --- a/app/Http/Controllers/Api/BaseApiController.php +++ b/app/Http/Controllers/Api/BaseApiController.php @@ -11,8 +11,9 @@ use Auth, Cache, Storage, URL; use Carbon\Carbon; use App\{ Avatar, - Notification, + Like, Media, + Notification, Profile, Status }; @@ -21,7 +22,8 @@ use App\Transformer\Api\{ NotificationTransformer, MediaTransformer, MediaDraftTransformer, - StatusTransformer + StatusTransformer, + StatusStatelessTransformer }; use League\Fractal; use App\Util\Media\Filter; @@ -338,4 +340,29 @@ class BaseApiController extends Controller $res = $this->fractal->createData($resource)->toArray(); return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES); } + + public function accountLikes(Request $request) + { + $user = $request->user(); + abort_if(!$request->user(), 403); + + $limit = 10; + $page = (int) $request->input('page', 1); + + if($page > 20) { + return []; + } + + $favourites = $user->profile->likes() + ->latest() + ->simplePaginate($limit) + ->pluck('status_id'); + + $statuses = Status::find($favourites)->reverse(); + + $resource = new Fractal\Resource\Collection($statuses, new StatusStatelessTransformer()); + $res = $this->fractal->createData($resource)->toArray(); + + 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 6fe9a5bdf..e3fc8c757 100644 --- a/app/Http/Controllers/InternalApiController.php +++ b/app/Http/Controllers/InternalApiController.php @@ -4,6 +4,7 @@ namespace App\Http\Controllers; use Illuminate\Http\Request; use App\{ + AccountInterstitial, DirectMessage, DiscoverCategory, Hashtag, @@ -213,6 +214,35 @@ class InternalApiController extends Controller ]) ->accessLevel('admin') ->save(); + + + 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(); + + $u = $status->profile->user; + $u->has_interstitial = true; + $u->save(); + } break; case 'remcw': @@ -231,6 +261,14 @@ class InternalApiController extends Controller ]) ->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; case 'unlist': @@ -250,6 +288,34 @@ class InternalApiController extends Controller ]) ->accessLevel('admin') ->save(); + + 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(); + + $u = $status->profile->user; + $u->has_interstitial = true; + $u->save(); + } break; } return ['msg' => 200]; @@ -364,6 +430,7 @@ class InternalApiController extends Controller NewStatusPipeline::dispatch($status); Cache::forget('user:account:id:'.$profile->user_id); + Cache::forget('_api:statuses:recent_9:'.$profile->id); Cache::forget('profile:status_count:'.$profile->id); Cache::forget($user->storageUsedKey()); return $status->url(); diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php index 7d0b4405c..6bd42d177 100644 --- a/app/Http/Controllers/ProfileController.php +++ b/app/Http/Controllers/ProfileController.php @@ -66,7 +66,9 @@ class ProfileController extends Controller 'list' => $settings->show_profile_followers ] ]; - return view('profile.show', compact('profile', 'settings')); + $ui = $request->has('ui') && $request->input('ui') == 'memory' ? 'profile.memory' : 'profile.show'; + + return view($ui, compact('profile', 'settings')); } else { $key = 'profile:settings:' . $user->id; $ttl = now()->addHours(6); @@ -103,7 +105,8 @@ class ProfileController extends Controller 'list' => $settings->show_profile_followers ] ]; - return view('profile.show', compact('profile', 'settings')); + $ui = $request->has('ui') && $request->input('ui') == 'memory' ? 'profile.memory' : 'profile.show'; + return view($ui, compact('profile', 'settings')); } } diff --git a/app/Http/Controllers/PublicApiController.php b/app/Http/Controllers/PublicApiController.php index 8dab9b65f..34757efa6 100644 --- a/app/Http/Controllers/PublicApiController.php +++ b/app/Http/Controllers/PublicApiController.php @@ -20,7 +20,8 @@ use League\Fractal; use App\Transformer\Api\{ AccountTransformer, RelationshipTransformer, - StatusTransformer + StatusTransformer, + StatusStatelessTransformer }; use App\Services\{ AccountService, @@ -86,6 +87,24 @@ class PublicApiController extends Controller $profile = Profile::whereUsername($username)->whereNull('status')->firstOrFail(); $status = Status::whereProfileId($profile->id)->findOrFail($postid); $this->scopeCheck($profile, $status); + if(!Auth::check()) { + $res = Cache::remember('wapi:v1:status:stateless_byid:' . $status->id, now()->addMinutes(30), function() use($status) { + $item = new Fractal\Resource\Item($status, new StatusStatelessTransformer()); + $res = [ + 'status' => $this->fractal->createData($item)->toArray(), + 'user' => [], + 'likes' => [], + 'shares' => [], + 'reactions' => [ + 'liked' => false, + 'shared' => false, + 'bookmarked' => false, + ], + ]; + return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES); + }); + return $res; + } $item = new Fractal\Resource\Item($status, new StatusTransformer()); $res = [ 'status' => $this->fractal->createData($item)->toArray(), @@ -419,7 +438,6 @@ class PublicApiController extends Controller } - public function networkTimelineApi(Request $request) { return response()->json([]); @@ -543,6 +561,50 @@ class PublicApiController extends Controller } } + $tag = in_array('private', $visibility) ? 'private' : 'public'; + if($min_id == 1 && $limit == 9 && $tag == 'public') { + $limit = 9; + $scope = ['photo', 'photo:album', 'video', 'video:album']; + $key = '_api:statuses:recent_9:'.$profile->id; + $res = Cache::remember($key, now()->addHours(24), function() use($profile, $scope, $visibility, $limit) { + $dir = '>'; + $id = 1; + $timeline = Status::select( + 'id', + 'uri', + 'caption', + 'rendered', + 'profile_id', + 'type', + 'in_reply_to_id', + 'reblog_of_id', + 'is_nsfw', + 'likes_count', + 'reblogs_count', + 'scope', + 'visibility', + 'local', + 'place_id', + 'comments_disabled', + 'cw_summary', + 'created_at', + 'updated_at' + )->whereProfileId($profile->id) + ->whereIn('type', $scope) + ->where('id', $dir, $id) + ->whereIn('visibility', $visibility) + ->limit($limit) + ->orderByDesc('id') + ->get(); + + $resource = new Fractal\Resource\Collection($timeline, new StatusStatelessTransformer()); + $res = $this->fractal->createData($resource)->toArray(); + + return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES); + }); + return $res; + } + $dir = $min_id ? '>' : '<'; $id = $min_id ?? $max_id; $timeline = Status::select( @@ -560,6 +622,8 @@ class PublicApiController extends Controller 'scope', 'visibility', 'local', + 'place_id', + 'comments_disabled', 'cw_summary', 'created_at', 'updated_at' diff --git a/app/Http/Controllers/StatusController.php b/app/Http/Controllers/StatusController.php index f98fa5d63..c0bf8e06d 100644 --- a/app/Http/Controllers/StatusController.php +++ b/app/Http/Controllers/StatusController.php @@ -6,6 +6,7 @@ use App\Jobs\ImageOptimizePipeline\ImageOptimize; use App\Jobs\StatusPipeline\NewStatusPipeline; use App\Jobs\StatusPipeline\StatusDelete; use App\Jobs\SharePipeline\SharePipeline; +use App\AccountInterstitial; use App\Media; use App\Profile; use App\Status; @@ -162,14 +163,49 @@ class StatusController extends Controller $status = Status::findOrFail($request->input('item')); - if ($status->profile_id === Auth::user()->profile->id || Auth::user()->is_admin == true) { + $user = Auth::user(); + + if($status->profile_id != $user->profile->id && + $user->is_admin == true && + $status->uri == null + ) { + $media = $status->media; + + $ai = new AccountInterstitial; + $ai->user_id = $status->profile->user_id; + $ai->type = 'post.removed'; + $ai->view = 'account.moderation.post.removed'; + $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(); + + $u = $status->profile->user; + $u->has_interstitial = true; + $u->save(); + } + + if ($status->profile_id == $user->profile->id || $user->is_admin == true) { Cache::forget('profile:status_count:'.$status->profile_id); StatusDelete::dispatch($status); } + if($request->wantsJson()) { return response()->json(['Status successfully deleted.']); } else { - return redirect(Auth::user()->url()); + return redirect($user->url()); } } diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 94597e211..c68302667 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -66,6 +66,7 @@ class Kernel extends HttpKernel 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'twofactor' => \App\Http\Middleware\TwoFactorAuth::class, 'validemail' => \App\Http\Middleware\EmailVerificationCheck::class, + 'interstitial' => \App\Http\Middleware\AccountInterstitial::class, // 'restricted' => \App\Http\Middleware\RestrictedAccess::class, ]; } diff --git a/app/Http/Middleware/AccountInterstitial.php b/app/Http/Middleware/AccountInterstitial.php new file mode 100644 index 000000000..19bf8ae1e --- /dev/null +++ b/app/Http/Middleware/AccountInterstitial.php @@ -0,0 +1,48 @@ +is($ar)) { + if($request->user()->has_interstitial) { + if($request->wantsJson()) { + $res = ['_refresh'=>true,'error' => 403, 'message' => \App\AccountInterstitial::JSON_MESSAGE]; + return response()->json($res, 403); + } else { + return redirect('/i/warning'); + } + } else { + return $next($request); + } + } else { + return $next($request); + } + } +} diff --git a/app/Jobs/DeletePipeline/DeleteAccountPipeline.php b/app/Jobs/DeletePipeline/DeleteAccountPipeline.php index c9777df5e..a225b48d9 100644 --- a/app/Jobs/DeletePipeline/DeleteAccountPipeline.php +++ b/app/Jobs/DeletePipeline/DeleteAccountPipeline.php @@ -10,6 +10,7 @@ use Illuminate\Foundation\Bus\Dispatchable; use DB; use Illuminate\Support\Str; use App\{ + AccountInterstitial, AccountLog, Activity, Avatar, @@ -68,6 +69,10 @@ class DeleteAccountPipeline implements ShouldQueue }); }); + DB::transaction(function() use ($user) { + AccountInterstitial::whereUserId($user->id)->delete(); + }); + DB::transaction(function() use ($user) { if($user->profile) { $avatar = $user->profile->avatar; @@ -79,6 +84,7 @@ class DeleteAccountPipeline implements ShouldQueue Bookmark::whereProfileId($user->profile_id)->forceDelete(); EmailVerification::whereUserId($user->id)->forceDelete(); StatusHashtag::whereProfileId($id)->delete(); + DirectMessage::whereFromId($user->profile_id)->delete(); FollowRequest::whereFollowingId($id) ->orWhere('follower_id', $id) ->forceDelete(); diff --git a/app/Status.php b/app/Status.php index 6232a3359..3b6f6120c 100644 --- a/app/Status.php +++ b/app/Status.php @@ -89,7 +89,8 @@ class Status extends Model public function thumb($showNsfw = false) { - return Cache::remember('status:thumb:'.$this->id, now()->addMinutes(15), function() use ($showNsfw) { + $key = $showNsfw ? 'status:thumb:nsfw1'.$this->id : 'status:thumb:nsfw0'.$this->id; + return Cache::remember($key, now()->addMinutes(15), function() use ($showNsfw) { $type = $this->type ?? $this->setType(); $is_nsfw = !$showNsfw ? $this->is_nsfw : false; if ($this->media->count() == 0 || $is_nsfw || !in_array($type,['photo', 'photo:album', 'video'])) { diff --git a/app/Transformer/Api/StatusStatelessTransformer.php b/app/Transformer/Api/StatusStatelessTransformer.php index 07eefb4a2..9023586e1 100644 --- a/app/Transformer/Api/StatusStatelessTransformer.php +++ b/app/Transformer/Api/StatusStatelessTransformer.php @@ -5,6 +5,8 @@ namespace App\Transformer\Api; use App\Status; use League\Fractal; use Cache; +use App\Services\HashidService; +use App\Services\MediaTagService; class StatusStatelessTransformer extends Fractal\TransformerAbstract { @@ -17,8 +19,11 @@ class StatusStatelessTransformer extends Fractal\TransformerAbstract public function transform(Status $status) { + $taggedPeople = MediaTagService::get($status->id); + return [ 'id' => (string) $status->id, + 'shortcode' => HashidService::encode($status->id), 'uri' => $status->url(), 'url' => $status->url(), 'in_reply_to_id' => $status->in_reply_to_id, @@ -42,13 +47,17 @@ class StatusStatelessTransformer extends Fractal\TransformerAbstract 'language' => null, 'pinned' => null, + 'mentions' => [], + 'tags' => [], 'pf_type' => $status->type ?? $status->setType(), 'reply_count' => (int) $status->reply_count, 'comments_disabled' => $status->comments_disabled ? true : false, 'thread' => false, 'replies' => [], - 'parent' => $status->parent() ? $this->transform($status->parent()) : [], + 'parent' => [], + 'place' => $status->place, 'local' => (bool) $status->local, + 'taggedPeople' => $taggedPeople ]; } diff --git a/app/User.php b/app/User.php index 717c4f316..4bd60a075 100644 --- a/app/User.php +++ b/app/User.php @@ -88,4 +88,9 @@ class User extends Authenticatable return $this->hasMany(AccountLog::class); } + public function interstitials() + { + return $this->hasMany(AccountInterstitial::class); + } + } diff --git a/app/Util/Blurhash/AC.php b/app/Util/Blurhash/AC.php new file mode 100644 index 000000000..a157c48b8 --- /dev/null +++ b/app/Util/Blurhash/AC.php @@ -0,0 +1,34 @@ + 0; + return $sign * pow(abs($base), $exp); + } +} \ No newline at end of file diff --git a/app/Util/Blurhash/Base83.php b/app/Util/Blurhash/Base83.php new file mode 100644 index 000000000..043ca2e60 --- /dev/null +++ b/app/Util/Blurhash/Base83.php @@ -0,0 +1,39 @@ + 9) || ($components_y < 1 || $components_y > 9)) { + throw new InvalidArgumentException("x and y component counts must be between 1 and 9 inclusive."); + } + $height = count($image); + $width = count($image[0]); + + $image_linear = $image; + if (!$linear) { + $image_linear = []; + for ($y = 0; $y < $height; $y++) { + $line = []; + for ($x = 0; $x < $width; $x++) { + $pixel = $image[$y][$x]; + $line[] = [ + Color::toLinear($pixel[0]), + Color::toLinear($pixel[1]), + Color::toLinear($pixel[2]) + ]; + } + $image_linear[] = $line; + } + } + + $components = []; + $scale = 1 / ($width * $height); + for ($y = 0; $y < $components_y; $y++) { + for ($x = 0; $x < $components_x; $x++) { + $normalisation = $x == 0 && $y == 0 ? 1 : 2; + $r = $g = $b = 0; + for ($i = 0; $i < $width; $i++) { + for ($j = 0; $j < $height; $j++) { + $color = $image_linear[$j][$i]; + $basis = $normalisation + * cos(M_PI * $i * $x / $width) + * cos(M_PI * $j * $y / $height); + + $r += $basis * $color[0]; + $g += $basis * $color[1]; + $b += $basis * $color[2]; + } + } + + $components[] = [ + $r * $scale, + $g * $scale, + $b * $scale + ]; + } + } + + $dc_value = DC::encode(array_shift($components) ?: []); + + $max_ac_component = 0; + foreach ($components as $component) { + $component[] = $max_ac_component; + $max_ac_component = max ($component); + } + + $quant_max_ac_component = (int) max(0, min(82, floor($max_ac_component * 166 - 0.5))); + $ac_component_norm_factor = ($quant_max_ac_component + 1) / 166; + + $ac_values = []; + foreach ($components as $component) { + $ac_values[] = AC::encode($component, $ac_component_norm_factor); + } + + $blurhash = Base83::encode($components_x - 1 + ($components_y - 1) * 9, 1); + $blurhash .= Base83::encode($quant_max_ac_component, 1); + $blurhash .= Base83::encode($dc_value, 4); + foreach ($ac_values as $ac_value) { + $blurhash .= Base83::encode((int) $ac_value, 2); + } + + return $blurhash; + } + + public static function decode (string $blurhash, int $width, int $height, float $punch = 1.0, bool $linear = false): array { + if (empty($blurhash) || strlen($blurhash) < 6) { + throw new InvalidArgumentException("Blurhash string must be at least 6 characters"); + } + + $size_info = Base83::decode($blurhash[0]); + $size_y = floor($size_info / 9) + 1; + $size_x = ($size_info % 9) + 1; + + $length = (int) strlen($blurhash); + $expected_length = (int) (4 + (2 * $size_y * $size_x)); + if ($length !== $expected_length) { + throw new InvalidArgumentException("Blurhash length mismatch: length is {$length} but it should be {$expected_length}"); + } + + $colors = [DC::decode(Base83::decode(substr($blurhash, 2, 4)))]; + + $quant_max_ac_component = Base83::decode($blurhash[1]); + $max_value = ($quant_max_ac_component + 1) / 166; + for ($i = 1; $i < $size_x * $size_y; $i++) { + $value = Base83::decode(substr($blurhash, 4 + $i * 2, 2)); + $colors[$i] = AC::decode($value, $max_value * $punch); + } + + $pixels = []; + for ($y = 0; $y < $height; $y++) { + $row = []; + for ($x = 0; $x < $width; $x++) { + $r = $g = $b = 0; + for ($j = 0; $j < $size_y; $j++) { + for ($i = 0; $i < $size_x; $i++) { + $color = $colors[$i + $j * $size_x]; + $basis = + cos((M_PI * $x * $i) / $width) * + cos((M_PI * $y * $j) / $height); + + $r += $color[0] * $basis; + $g += $color[1] * $basis; + $b += $color[2] * $basis; + } + } + + $row[] = $linear ? [$r, $g, $b] : [ + Color::toSRGB($r), + Color::toSRGB($g), + Color::toSRGB($b) + ]; + } + $pixels[] = $row; + } + + return $pixels; + } +} \ No newline at end of file diff --git a/app/Util/Blurhash/Color.php b/app/Util/Blurhash/Color.php new file mode 100644 index 000000000..4b64c32f7 --- /dev/null +++ b/app/Util/Blurhash/Color.php @@ -0,0 +1,19 @@ +> 16; + $g = ($value >> 8) & 255; + $b = $value & 255; + return [ + Color::toLinear($r), + Color::toLinear($g), + Color::toLinear($b) + ]; + } +} \ No newline at end of file diff --git a/app/Util/Media/Blurhash.php b/app/Util/Media/Blurhash.php new file mode 100644 index 000000000..2be054e02 --- /dev/null +++ b/app/Util/Media/Blurhash.php @@ -0,0 +1,47 @@ +mime, ['image/png', 'image/jpeg'])) { + return; + } + + $file = storage_path('app/' . $media->thumbnail_path); + + if(!is_file($file)) { + return; + } + + $image = imagecreatefromstring(file_get_contents($file)); + $width = imagesx($image); + $height = imagesy($image); + + $pixels = []; + for ($y = 0; $y < $height; ++$y) { + $row = []; + for ($x = 0; $x < $width; ++$x) { + $index = imagecolorat($image, $x, $y); + $colors = imagecolorsforindex($image, $index); + + $row[] = [$colors['red'], $colors['green'], $colors['blue']]; + } + $pixels[] = $row; + } + + $components_x = 4; + $components_y = 4; + $blurhash = BlurhashEngine::encode($pixels, $components_x, $components_y); + if(strlen($blurhash) > 191) { + return; + } + return $blurhash; + } + +} \ No newline at end of file diff --git a/app/Util/Media/Image.php b/app/Util/Media/Image.php index bbc877ffa..0cf359237 100644 --- a/app/Util/Media/Image.php +++ b/app/Util/Media/Image.php @@ -182,6 +182,10 @@ class Image $media->save(); + + if($thumbnail) { + $this->generateBlurhash($media); + } Cache::forget('status:transformer:media:attachments:'.$media->status_id); Cache::forget('status:thumb:'.$media->status_id); } catch (Exception $e) { @@ -198,4 +202,13 @@ class Image return ['path' => $basePath, 'png' => $png]; } + + protected function generateBlurhash($media) + { + $blurhash = Blurhash::generate($media); + if($blurhash) { + $media->blurhash = $blurhash; + $media->save(); + } + } } diff --git a/database/migrations/2020_12_01_073200_add_indexes_to_likes_table.php b/database/migrations/2020_12_01_073200_add_indexes_to_likes_table.php new file mode 100644 index 000000000..1a1256fbb --- /dev/null +++ b/database/migrations/2020_12_01_073200_add_indexes_to_likes_table.php @@ -0,0 +1,34 @@ +index('profile_id', 'likes_profile_id_index'); + $table->index('status_id', 'likes_status_id_index'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('likes', function (Blueprint $table) { + $table->dropIndex('likes_profile_id_index'); + $table->dropIndex('likes_status_id_index'); + }); + } +} diff --git a/database/migrations/2020_12_03_050018_create_account_interstitials_table.php b/database/migrations/2020_12_03_050018_create_account_interstitials_table.php new file mode 100644 index 000000000..0b0e8862c --- /dev/null +++ b/database/migrations/2020_12_03_050018_create_account_interstitials_table.php @@ -0,0 +1,53 @@ +id(); + $table->unsignedInteger('user_id')->nullable()->index(); + $table->string('type')->nullable(); + $table->string('view')->nullable(); + $table->bigInteger('item_id')->unsigned()->nullable(); + $table->string('item_type')->nullable(); + $table->boolean('has_media')->default(false)->nullable(); + $table->string('blurhash')->nullable(); + $table->text('message')->nullable(); + $table->text('violation_header')->nullable(); + $table->text('violation_body')->nullable(); + $table->json('meta')->nullable(); + $table->text('appeal_message')->nullable(); + $table->timestamp('appeal_requested_at')->nullable()->index(); + $table->timestamp('appeal_handled_at')->nullable()->index(); + $table->timestamp('read_at')->nullable()->index(); + $table->timestamps(); + }); + + Schema::table('users', function(Blueprint $table) { + $table->boolean('has_interstitial')->default(false)->index(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('account_interstitials'); + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('has_interstitial'); + }); + } +} diff --git a/package-lock.json b/package-lock.json index 494c83bfc..9ed8b9ea3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1649,6 +1649,11 @@ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.1.tgz", "integrity": "sha512-DdmyoGCleJnkbp3nkbxTLJ18rjDsE4yCggEwKNXkeV123sPNfOCYeDoeuOY+F2FrSjO1YXcTU+dsy96KMy+gcg==" }, + "blurhash": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/blurhash/-/blurhash-1.1.3.tgz", + "integrity": "sha512-yUhPJvXexbqbyijCIE/T2NCXcj9iNPhWmOKbPTuR/cm7Q5snXYIfnVnz6m7MWOXxODMz/Cr3UcVkRdHiuDVRDw==" + }, "bn.js": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.1.tgz", diff --git a/package.json b/package.json index 4c1ccd439..3a6285a0b 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "dependencies": { "@trevoreyre/autocomplete-vue": "^2.2.0", "animate.css": "^4.1.0", + "blurhash": "^1.1.3", "bootstrap-vue": "^2.16.0", "filesize": "^3.6.1", "howler": "^2.2.0", diff --git a/public/js/activity.js b/public/js/activity.js index 66eea31c7..4fdce4aea 100644 --- a/public/js/activity.js +++ b/public/js/activity.js @@ -1 +1 @@ -(window.webpackJsonp=window.webpackJsonp||[]).push([[3],{1:function(t,a,e){t.exports=e("hUgz")},"KHd+":function(t,a,e){"use strict";function n(t,a,e,n,o,s,i,r){var c,l="function"==typeof t?t.options:t;if(a&&(l.render=a,l.staticRenderFns=e,l._compiled=!0),n&&(l.functional=!0),s&&(l._scopeId="data-v-"+s),i?(c=function(t){(t=t||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(t=__VUE_SSR_CONTEXT__),o&&o.call(this,t),t&&t._registeredComponents&&t._registeredComponents.add(i)},l._ssrRegister=c):o&&(c=r?function(){o.call(this,this.$root.$options.shadowRoot)}:o),c)if(l.functional){l._injectStyles=c;var u=l.render;l.render=function(t,a){return c.call(a),u(t,a)}}else{var d=l.beforeCreate;l.beforeCreate=d?[].concat(d,c):[c]}return{exports:t,options:l}}e.d(a,"a",(function(){return n}))},hUgz:function(t,a,e){Vue.component("activity-component",e("tXHz").default)},tXHz:function(t,a,e){"use strict";e.r(a);function n(t){return function(t){if(Array.isArray(t)){for(var a=0,e=new Array(t.length);a10?t.complete():axios.get("/api/pixelfed/v1/notifications",{params:{pg:!0,page:this.notificationCursor}}).then((function(e){if(e.data.length){var o,s=e.data.filter((function(t){return!("share"==t.type&&!status)&&!_.find(a.notifications,{id:t.id})}));(o=a.notifications).push.apply(o,n(s)),a.notificationCursor++,t.loaded()}else t.complete()}))},truncate:function(t){return t.length<=15?t:t.slice(0,15)+"..."},timeAgo:function(t){var a=Date.parse(t),e=Math.floor((new Date-a)/1e3),n=Math.floor(e/31536e3);return n>=1?n+"y":(n=Math.floor(e/604800))>=1?n+"w":(n=Math.floor(e/86400))>=1?n+"d":(n=Math.floor(e/3600))>=1?n+"h":(n=Math.floor(e/60))>=1?n+"m":Math.floor(e)+"s"},mentionUrl:function(t){return"/p/"+t.account.username+"/"+t.id},followProfile:function(t){var a=this,e=t.account.id;axios.post("/i/follow",{item:e}).then((function(t){a.notifications.map((function(t){t.account.id===e&&(t.relationship.following=!0)}))})).catch((function(t){t.response.data.message&&swal("Error",t.response.data.message,"error")}))},viewContext:function(t){switch(t.type){case"follow":return t.account.url;case"mention":return t.status.url;case"like":case"favourite":case"comment":return t.status.url;case"tagged":return t.tagged.post_url;case"direct":return"/account/direct/t/"+t.account.id}return"/"}}},s=e("KHd+"),i=Object(s.a)(o,(function(){var t=this,a=t.$createElement,e=t._self._c||a;return e("div",[e("div",{staticClass:"container"},[e("div",{staticClass:"row my-5"},[e("div",{staticClass:"col-12 col-md-8 offset-md-2"},[t._l(t.notifications,(function(a,n){return t.notifications.length>0?e("div",{staticClass:"media mb-3 align-items-center px-3 border-bottom pb-3"},[e("img",{staticClass:"mr-2 rounded-circle",staticStyle:{border:"1px solid #ccc"},attrs:{src:a.account.avatar,alt:"",width:"32px",height:"32px"}}),t._v(" "),e("div",{staticClass:"media-body font-weight-light"},["favourite"==a.type?e("div",[e("p",{staticClass:"my-0"},[e("a",{staticClass:"font-weight-bold text-dark word-break",attrs:{href:a.account.url,"data-placement":"bottom","data-toggle":"tooltip",title:a.account.username}},[t._v(t._s(t.truncate(a.account.username)))]),t._v(" liked your "),e("a",{staticClass:"font-weight-bold",attrs:{href:a.status.url}},[t._v("post")]),t._v(".\n\t\t\t\t\t\t\t")])]):"comment"==a.type?e("div",[e("p",{staticClass:"my-0"},[e("a",{staticClass:"font-weight-bold text-dark word-break",attrs:{href:a.account.url,"data-placement":"bottom","data-toggle":"tooltip",title:a.account.username}},[t._v(t._s(t.truncate(a.account.username)))]),t._v(" commented on your "),e("a",{staticClass:"font-weight-bold",attrs:{href:a.status.url}},[t._v("post")]),t._v(".\n\t\t\t\t\t\t\t")])]):"mention"==a.type?e("div",[e("p",{staticClass:"my-0"},[e("a",{staticClass:"font-weight-bold text-dark word-break",attrs:{href:a.account.url,"data-placement":"bottom","data-toggle":"tooltip",title:a.account.username}},[t._v(t._s(t.truncate(a.account.username)))]),t._v(" "),e("a",{staticClass:"font-weight-bold",attrs:{href:t.mentionUrl(a.status)}},[t._v("mentioned")]),t._v(" you.\n\t\t\t\t\t\t\t")])]):"follow"==a.type?e("div",[e("p",{staticClass:"my-0"},[e("a",{staticClass:"font-weight-bold text-dark word-break",attrs:{href:a.account.url,"data-placement":"bottom","data-toggle":"tooltip",title:a.account.username}},[t._v(t._s(t.truncate(a.account.username)))]),t._v(" followed you.\n\t\t\t\t\t\t\t")])]):"share"==a.type?e("div",[e("p",{staticClass:"my-0"},[e("a",{staticClass:"font-weight-bold text-dark word-break",attrs:{href:a.account.url,"data-placement":"bottom","data-toggle":"tooltip",title:a.account.username}},[t._v(t._s(t.truncate(a.account.username)))]),t._v(" shared your "),e("a",{staticClass:"font-weight-bold",attrs:{href:a.status.reblog.url}},[t._v("post")]),t._v(".\n\t\t\t\t\t\t\t")])]):"modlog"==a.type?e("div",[e("p",{staticClass:"my-0"},[e("a",{staticClass:"font-weight-bold text-dark word-break",attrs:{href:a.account.url,title:a.account.username}},[t._v(t._s(t.truncate(a.account.username)))]),t._v(" updated a "),e("a",{staticClass:"font-weight-bold",attrs:{href:a.modlog.url}},[t._v("modlog")]),t._v(".\n\t\t\t\t\t\t\t")])]):"tagged"==a.type?e("div",[e("p",{staticClass:"my-0"},[e("a",{staticClass:"font-weight-bold text-dark word-break",attrs:{href:a.account.url,title:a.account.username}},[t._v(t._s(t.truncate(a.account.username)))]),t._v(" tagged you in a "),e("a",{staticClass:"font-weight-bold",attrs:{href:a.tagged.post_url}},[t._v("post")]),t._v(".\n\t\t\t\t\t\t\t")])]):"direct"==a.type?e("div",[e("p",{staticClass:"my-0"},[e("a",{staticClass:"font-weight-bold text-dark word-break",attrs:{href:a.account.url,title:a.account.username}},[t._v(t._s(t.truncate(a.account.username)))]),t._v(" sent a "),e("a",{staticClass:"font-weight-bold",attrs:{href:"/account/direct/t/"+a.account.id}},[t._v("dm")]),t._v(".\n\t\t\t\t\t\t")])]):t._e(),t._v(" "),e("div",{staticClass:"align-items-center"},[e("span",{staticClass:"small text-muted",attrs:{"data-toggle":"tooltip","data-placement":"bottom",title:a.created_at}},[t._v(t._s(t.timeAgo(a.created_at)))])])]),t._v(" "),e("div",[a.status&&a.status&&a.status.media_attachments&&a.status.media_attachments.length?e("div",[e("a",{attrs:{href:a.status.url}},[e("img",{attrs:{src:a.status.media_attachments[0].preview_url,width:"32px",height:"32px"}})])]):a.status&&a.status.parent&&a.status.parent.media_attachments&&a.status.parent.media_attachments.length?e("div",[e("a",{attrs:{href:a.status.parent.url}},[e("img",{attrs:{src:a.status.parent.media_attachments[0].preview_url,width:"32px",height:"32px"}})])]):e("div",[e("a",{staticClass:"btn btn-outline-primary py-0 font-weight-bold",attrs:{href:t.viewContext(a)}},[t._v("View")])])])]):t._e()})),t._v(" "),t.notifications.length?e("div",[e("infinite-loading",{on:{infinite:t.infiniteNotifications}},[e("div",{staticClass:"font-weight-bold",attrs:{slot:"no-results"},slot:"no-results"}),t._v(" "),e("div",{staticClass:"font-weight-bold",attrs:{slot:"no-more"},slot:"no-more"})])],1):t._e(),t._v(" "),0==t.notifications.length?e("div",{staticClass:"text-lighter text-center py-3"},[t._m(0),t._v(" "),e("p",{staticClass:"mb-0 small font-weight-bold"},[t._v("0 Notifications!")])]):t._e()],2)])])])}),[function(){var t=this.$createElement,a=this._self._c||t;return a("p",{staticClass:"mb-0"},[a("i",{staticClass:"fas fa-inbox fa-3x"})])}],!1,null,null,null);a.default=i.exports}},[[1,0]]]); \ No newline at end of file +(window.webpackJsonp=window.webpackJsonp||[]).push([[3],{1:function(t,a,e){t.exports=e("hUgz")},"KHd+":function(t,a,e){"use strict";function n(t,a,e,n,o,s,i,r){var c,l="function"==typeof t?t.options:t;if(a&&(l.render=a,l.staticRenderFns=e,l._compiled=!0),n&&(l.functional=!0),s&&(l._scopeId="data-v-"+s),i?(c=function(t){(t=t||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(t=__VUE_SSR_CONTEXT__),o&&o.call(this,t),t&&t._registeredComponents&&t._registeredComponents.add(i)},l._ssrRegister=c):o&&(c=r?function(){o.call(this,this.$root.$options.shadowRoot)}:o),c)if(l.functional){l._injectStyles=c;var u=l.render;l.render=function(t,a){return c.call(a),u(t,a)}}else{var d=l.beforeCreate;l.beforeCreate=d?[].concat(d,c):[c]}return{exports:t,options:l}}e.d(a,"a",(function(){return n}))},hUgz:function(t,a,e){Vue.component("activity-component",e("tXHz").default)},tXHz:function(t,a,e){"use strict";e.r(a);function n(t){return function(t){if(Array.isArray(t)){for(var a=0,e=new Array(t.length);a10?t.complete():axios.get("/api/pixelfed/v1/notifications",{params:{pg:!0,page:this.notificationCursor}}).then((function(e){if(e.data.length){var o,s=e.data.filter((function(t){return!("share"==t.type&&!status)&&!_.find(a.notifications,{id:t.id})}));(o=a.notifications).push.apply(o,n(s)),a.notificationCursor++,t.loaded()}else t.complete()}))},truncate:function(t){return t.length<=15?t:t.slice(0,15)+"..."},timeAgo:function(t){var a=Date.parse(t),e=Math.floor((new Date-a)/1e3),n=Math.floor(e/31536e3);return n>=1?n+"y":(n=Math.floor(e/604800))>=1?n+"w":(n=Math.floor(e/86400))>=1?n+"d":(n=Math.floor(e/3600))>=1?n+"h":(n=Math.floor(e/60))>=1?n+"m":Math.floor(e)+"s"},mentionUrl:function(t){return"/p/"+t.account.username+"/"+t.id},followProfile:function(t){var a=this,e=t.account.id;axios.post("/i/follow",{item:e}).then((function(t){a.notifications.map((function(t){t.account.id===e&&(t.relationship.following=!0)}))})).catch((function(t){t.response.data.message&&swal("Error",t.response.data.message,"error")}))},viewContext:function(t){switch(t.type){case"follow":return t.account.url;case"mention":return t.status.url;case"like":case"favourite":case"comment":return t.status.url;case"tagged":return t.tagged.post_url;case"direct":return"/account/direct/t/"+t.account.id}return"/"}}},s=e("KHd+"),i=Object(s.a)(o,(function(){var t=this,a=t.$createElement,e=t._self._c||a;return e("div",[e("div",{staticClass:"container"},[e("div",{staticClass:"row my-5"},[e("div",{staticClass:"col-12 col-md-8 offset-md-2"},[t._l(t.notifications,(function(a,n){return t.notifications.length>0?e("div",{staticClass:"media mb-3 align-items-center px-3 border-bottom pb-3"},[e("img",{staticClass:"mr-2 rounded-circle",staticStyle:{border:"1px solid #ccc"},attrs:{src:a.account.avatar,alt:"",width:"32px",height:"32px"}}),t._v(" "),e("div",{staticClass:"media-body font-weight-light"},["favourite"==a.type?e("div",[e("p",{staticClass:"my-0"},[e("a",{staticClass:"font-weight-bold text-dark word-break",attrs:{href:a.account.url,"data-placement":"bottom","data-toggle":"tooltip",title:a.account.username}},[t._v(t._s(t.truncate(a.account.username)))]),t._v(" liked your "),e("a",{staticClass:"font-weight-bold",attrs:{href:a.status.url}},[t._v("post")]),t._v(".\n\t\t\t\t\t\t\t")])]):"comment"==a.type?e("div",[e("p",{staticClass:"my-0"},[e("a",{staticClass:"font-weight-bold text-dark word-break",attrs:{href:a.account.url,"data-placement":"bottom","data-toggle":"tooltip",title:a.account.username}},[t._v(t._s(t.truncate(a.account.username)))]),t._v(" commented on your "),e("a",{staticClass:"font-weight-bold",attrs:{href:a.status.url}},[t._v("post")]),t._v(".\n\t\t\t\t\t\t\t")])]):"mention"==a.type?e("div",[e("p",{staticClass:"my-0"},[e("a",{staticClass:"font-weight-bold text-dark word-break",attrs:{href:a.account.url,"data-placement":"bottom","data-toggle":"tooltip",title:a.account.username}},[t._v(t._s(t.truncate(a.account.username)))]),t._v(" "),e("a",{staticClass:"font-weight-bold",attrs:{href:t.mentionUrl(a.status)}},[t._v("mentioned")]),t._v(" you.\n\t\t\t\t\t\t\t")])]):"follow"==a.type?e("div",[e("p",{staticClass:"my-0"},[e("a",{staticClass:"font-weight-bold text-dark word-break",attrs:{href:a.account.url,"data-placement":"bottom","data-toggle":"tooltip",title:a.account.username}},[t._v(t._s(t.truncate(a.account.username)))]),t._v(" followed you.\n\t\t\t\t\t\t\t")])]):"share"==a.type?e("div",[e("p",{staticClass:"my-0"},[e("a",{staticClass:"font-weight-bold text-dark word-break",attrs:{href:a.account.url,"data-placement":"bottom","data-toggle":"tooltip",title:a.account.username}},[t._v(t._s(t.truncate(a.account.username)))]),t._v(" shared your "),e("a",{staticClass:"font-weight-bold",attrs:{href:a.status.reblog.url}},[t._v("post")]),t._v(".\n\t\t\t\t\t\t\t")])]):"modlog"==a.type?e("div",[e("p",{staticClass:"my-0"},[e("a",{staticClass:"font-weight-bold text-dark word-break",attrs:{href:a.account.url,title:a.account.username}},[t._v(t._s(t.truncate(a.account.username)))]),t._v(" updated a "),e("a",{staticClass:"font-weight-bold",attrs:{href:a.modlog.url}},[t._v("modlog")]),t._v(".\n\t\t\t\t\t\t\t")])]):"tagged"==a.type?e("div",[e("p",{staticClass:"my-0"},[e("a",{staticClass:"font-weight-bold text-dark word-break",attrs:{href:a.account.url,title:a.account.username}},[t._v(t._s(t.truncate(a.account.username)))]),t._v(" tagged you in a "),e("a",{staticClass:"font-weight-bold",attrs:{href:a.tagged.post_url}},[t._v("post")]),t._v(".\n\t\t\t\t\t\t\t")])]):"direct"==a.type?e("div",[e("p",{staticClass:"my-0"},[e("a",{staticClass:"font-weight-bold text-dark word-break",attrs:{href:a.account.url,title:a.account.username}},[t._v(t._s(t.truncate(a.account.username)))]),t._v(" sent a "),e("a",{staticClass:"font-weight-bold",attrs:{href:"/account/direct/t/"+a.account.id}},[t._v("dm")]),t._v(".\n\t\t\t\t\t\t")])]):t._e(),t._v(" "),e("div",{staticClass:"align-items-center"},[e("span",{staticClass:"small text-muted",attrs:{"data-toggle":"tooltip","data-placement":"bottom",title:a.created_at}},[t._v(t._s(t.timeAgo(a.created_at)))])])]),t._v(" "),e("div",[a.status&&a.status&&a.status.media_attachments&&a.status.media_attachments.length?e("div",[e("a",{attrs:{href:a.status.url}},[e("img",{attrs:{src:a.status.media_attachments[0].preview_url,width:"32px",height:"32px"}})])]):a.status&&a.status.parent&&a.status.parent.media_attachments&&a.status.parent.media_attachments.length?e("div",[e("a",{attrs:{href:a.status.parent.url}},[e("img",{attrs:{src:a.status.parent.media_attachments[0].preview_url,width:"32px",height:"32px"}})])]):e("div",[e("a",{staticClass:"btn btn-outline-primary py-0 font-weight-bold",attrs:{href:t.viewContext(a)}},[t._v("View")])])])]):t._e()})),t._v(" "),t.notifications.length?e("div",[e("infinite-loading",{on:{infinite:t.infiniteNotifications}},[e("div",{staticClass:"font-weight-bold",attrs:{slot:"no-results"},slot:"no-results"}),t._v(" "),e("div",{staticClass:"font-weight-bold",attrs:{slot:"no-more"},slot:"no-more"})])],1):t._e(),t._v(" "),0==t.notifications.length?e("div",{staticClass:"text-lighter text-center py-3"},[t._m(0),t._v(" "),e("p",{staticClass:"mb-0 small font-weight-bold"},[t._v("0 Notifications!")])]):t._e()],2)])])])}),[function(){var t=this.$createElement,a=this._self._c||t;return a("p",{staticClass:"mb-0"},[a("i",{staticClass:"fas fa-inbox fa-3x"})])}],!1,null,null,null);a.default=i.exports}},[[1,0]]]); \ No newline at end of file diff --git a/public/js/app.js b/public/js/app.js index 76304486c..dbad08097 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -1 +1 @@ -(window.webpackJsonp=window.webpackJsonp||[]).push([[4],{"+lRy":function(e,t){},0:function(e,t,r){r("JO1w"),r("+lRy"),r("xWuY"),r("YfGV"),e.exports=r("RvBz")},"8oxB":function(e,t){var r,n,i=e.exports={};function o(){throw new Error("setTimeout has not been defined")}function a(){throw new Error("clearTimeout has not been defined")}function s(e){if(r===setTimeout)return setTimeout(e,0);if((r===o||!r)&&setTimeout)return r=setTimeout,setTimeout(e,0);try{return r(e,0)}catch(t){try{return r.call(null,e,0)}catch(t){return r.call(this,e,0)}}}!function(){try{r="function"==typeof setTimeout?setTimeout:o}catch(e){r=o}try{n="function"==typeof clearTimeout?clearTimeout:a}catch(e){n=a}}();var l,c=[],u=!1,f=-1;function h(){u&&l&&(u=!1,l.length?c=l.concat(c):f=-1,c.length&&p())}function p(){if(!u){var e=s(h);u=!0;for(var t=c.length;t;){for(l=c,c=[];++f1)for(var r=1;r5&&r.startsWith("https://")){var n=new URL(r);n.host!==window.location.host&&"/i/redirect"!==n.pathname&&e.setAttribute("href","/i/redirect?url="+encodeURIComponent(r))}}))},window.App.boot=function(){new Vue({el:"#content"})},window.App.util={compose:{post:function(){var e=window.location.pathname;["/","/timeline/public"].includes(e)?$("#composeModal").modal("show"):window.location.href="/?a=co"},circle:function(){console.log("Unsupported method.")},collection:function(){console.log("Unsupported method.")},loop:function(){console.log("Unsupported method.")},story:function(){console.log("Unsupported method.")}},time:function(){return new Date},version:1,format:{count:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"en-GB",r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"compact";return e<1?0:new Intl.NumberFormat(t,{notation:r,compactDisplay:"short"}).format(e)},timeAgo:function(e){var t=Date.parse(e),r=Math.floor((new Date-t)/1e3),n=Math.floor(r/63072e3);return n>=1?n+"y":(n=Math.floor(r/604800))>=1?n+"w":(n=Math.floor(r/86400))>=1?n+"d":(n=Math.floor(r/3600))>=1?n+"h":(n=Math.floor(r/60))>=1?n+"m":Math.floor(r)+"s"}},filters:[["1977","filter-1977"],["Aden","filter-aden"],["Amaro","filter-amaro"],["Ashby","filter-ashby"],["Brannan","filter-brannan"],["Brooklyn","filter-brooklyn"],["Charmes","filter-charmes"],["Clarendon","filter-clarendon"],["Crema","filter-crema"],["Dogpatch","filter-dogpatch"],["Earlybird","filter-earlybird"],["Gingham","filter-gingham"],["Ginza","filter-ginza"],["Hefe","filter-hefe"],["Helena","filter-helena"],["Hudson","filter-hudson"],["Inkwell","filter-inkwell"],["Kelvin","filter-kelvin"],["Kuno","filter-juno"],["Lark","filter-lark"],["Lo-Fi","filter-lofi"],["Ludwig","filter-ludwig"],["Maven","filter-maven"],["Mayfair","filter-mayfair"],["Moon","filter-moon"],["Nashville","filter-nashville"],["Perpetua","filter-perpetua"],["Poprocket","filter-poprocket"],["Reyes","filter-reyes"],["Rise","filter-rise"],["Sierra","filter-sierra"],["Skyline","filter-skyline"],["Slumber","filter-slumber"],["Stinson","filter-stinson"],["Sutro","filter-sutro"],["Toaster","filter-toaster"],["Valencia","filter-valencia"],["Vesper","filter-vesper"],["Walden","filter-walden"],["Willow","filter-willow"],["X-Pro II","filter-xpro-ii"]],filterCss:{"filter-1977":"sepia(.5) hue-rotate(-30deg) saturate(1.4)","filter-aden":"sepia(.2) brightness(1.15) saturate(1.4)","filter-amaro":"sepia(.35) contrast(1.1) brightness(1.2) saturate(1.3)","filter-ashby":"sepia(.5) contrast(1.2) saturate(1.8)","filter-brannan":"sepia(.4) contrast(1.25) brightness(1.1) saturate(.9) hue-rotate(-2deg)","filter-brooklyn":"sepia(.25) contrast(1.25) brightness(1.25) hue-rotate(5deg)","filter-charmes":"sepia(.25) contrast(1.25) brightness(1.25) saturate(1.35) hue-rotate(-5deg)","filter-clarendon":"sepia(.15) contrast(1.25) brightness(1.25) hue-rotate(5deg)","filter-crema":"sepia(.5) contrast(1.25) brightness(1.15) saturate(.9) hue-rotate(-2deg)","filter-dogpatch":"sepia(.35) saturate(1.1) contrast(1.5)","filter-earlybird":"sepia(.25) contrast(1.25) brightness(1.15) saturate(.9) hue-rotate(-5deg)","filter-gingham":"contrast(1.1) brightness(1.1)","filter-ginza":"sepia(.25) contrast(1.15) brightness(1.2) saturate(1.35) hue-rotate(-5deg)","filter-hefe":"sepia(.4) contrast(1.5) brightness(1.2) saturate(1.4) hue-rotate(-10deg)","filter-helena":"sepia(.5) contrast(1.05) brightness(1.05) saturate(1.35)","filter-hudson":"sepia(.25) contrast(1.2) brightness(1.2) saturate(1.05) hue-rotate(-15deg)","filter-inkwell":"brightness(1.25) contrast(.85) grayscale(1)","filter-kelvin":"sepia(.15) contrast(1.5) brightness(1.1) hue-rotate(-10deg)","filter-juno":"sepia(.35) contrast(1.15) brightness(1.15) saturate(1.8)","filter-lark":"sepia(.25) contrast(1.2) brightness(1.3) saturate(1.25)","filter-lofi":"saturate(1.1) contrast(1.5)","filter-ludwig":"sepia(.25) contrast(1.05) brightness(1.05) saturate(2)","filter-maven":"sepia(.35) contrast(1.05) brightness(1.05) saturate(1.75)","filter-mayfair":"contrast(1.1) brightness(1.15) saturate(1.1)","filter-moon":"brightness(1.4) contrast(.95) saturate(0) sepia(.35)","filter-nashville":"sepia(.25) contrast(1.5) brightness(.9) hue-rotate(-15deg)","filter-perpetua":"contrast(1.1) brightness(1.25) saturate(1.1)","filter-poprocket":"sepia(.15) brightness(1.2)","filter-reyes":"sepia(.75) contrast(.75) brightness(1.25) saturate(1.4)","filter-rise":"sepia(.25) contrast(1.25) brightness(1.2) saturate(.9)","filter-sierra":"sepia(.25) contrast(1.5) brightness(.9) hue-rotate(-15deg)","filter-skyline":"sepia(.15) contrast(1.25) brightness(1.25) saturate(1.2)","filter-slumber":"sepia(.35) contrast(1.25) saturate(1.25)","filter-stinson":"sepia(.35) contrast(1.25) brightness(1.1) saturate(1.25)","filter-sutro":"sepia(.4) contrast(1.2) brightness(.9) saturate(1.4) hue-rotate(-10deg)","filter-toaster":"sepia(.25) contrast(1.5) brightness(.95) hue-rotate(-15deg)","filter-valencia":"sepia(.25) contrast(1.1) brightness(1.1)","filter-vesper":"sepia(.35) contrast(1.15) brightness(1.2) saturate(1.3)","filter-walden":"sepia(.35) contrast(.8) brightness(1.25) saturate(1.4)","filter-willow":"brightness(1.2) contrast(.85) saturate(.05) sepia(.2)","filter-xpro-ii":"sepia(.45) contrast(1.25) brightness(1.75) saturate(1.3) hue-rotate(-5deg)"},emoji:["😂","💯","❤️","🙌","👏","👌","😍","😯","😢","😅","😁","🙂","😎","😀","🤣","😃","😄","😆","😉","😊","😋","😘","😗","😙","😚","🤗","🤩","🤔","🤨","😐","😑","😶","🙄","😏","😣","😥","😮","🤐","😪","😫","😴","😌","😛","😜","😝","🤤","😒","😓","😔","😕","🙃","🤑","😲","🙁","😖","😞","😟","😤","😭","😦","😧","😨","😩","🤯","😬","😰","😱","😳","🤪","😵","😡","😠","🤬","😷","🤒","🤕","🤢","🤮","🤧","😇","🤠","🤡","🤥","🤫","🤭","🧐","🤓","😈","👿","👹","👺","💀","👻","👽","🤖","💩","😺","😸","😹","😻","😼","😽","🙀","😿","😾","🤲","👐","🤝","👍","👎","👊","✊","🤛","🤜","🤞","✌️","🤟","🤘","👈","👉","👆","👇","☝️","✋","🤚","🖐","🖖","👋","🤙","💪","🖕","✍️","🙏","💍","💄","💋","👄","👅","👂","👃","👣","👁","👀","🧠","🗣","👤","👥"],embed:{post:function(e){var t=!(arguments.length>1&&void 0!==arguments[1])||arguments[1],r=arguments.length>2&&void 0!==arguments[2]&&arguments[2],n=arguments.length>3&&void 0!==arguments[3]?arguments[3]:"full",i=e+"/embed?";return i+=t?"caption=true&":"caption=false&",i+=r?"likes=true&":"likes=false&",' +@if($interstitial->blurhash) + +@endif +@endpush \ No newline at end of file diff --git a/resources/views/account/moderation/post/removed.blade.php b/resources/views/account/moderation/post/removed.blade.php new file mode 100644 index 000000000..123863489 --- /dev/null +++ b/resources/views/account/moderation/post/removed.blade.php @@ -0,0 +1,99 @@ +@extends('layouts.blank') + +@section('content') + +
+
+
+

Your Post Has Been Deleted

+

We removed your post because it doesn't follow our Community Guidelines. If you violate our guidelines again, your account may be restricted or disabled.

+

To continue you must click the "I Understand" button at the bottom of this page.

+
+
+
+
+
+

Post Details

+ @if($interstitial->has_media) +
+
+ @if($interstitial->blurhash) + + @else + No preview available + @endif +
+
+ @if($meta->caption) +

+ Caption: {{$meta->caption}} +

+ @endif +

+ Like Count: {{$meta->likes_count}} +

+

+ Share Count: {{$meta->reblogs_count}} +

+

+ Timestamp: {{now()->parse($meta->created_at)->format('r')}} +

+

+ URL: {{$meta->url}} +

+
+
+ @else +
+
+

+ Comment: {{$meta->caption}} +

+

+ Posted on {{$meta->created_at}} +

+

+ {{$meta->url}} +

+
+
+ @endif +
+
+
+

Review the Community Guidelines

+

We want to keep {{config('app.name')}} a safe place for everyone, and we created these Community Guidelines to support and protect our community.

+
+
+
+
+ @csrf + + + + + +
+
+
+
+@endsection + +@push('scripts') + +@if($interstitial->blurhash) + +@endif +@endpush \ No newline at end of file diff --git a/resources/views/account/moderation/post/unlist.blade.php b/resources/views/account/moderation/post/unlist.blade.php new file mode 100644 index 000000000..3c86acb76 --- /dev/null +++ b/resources/views/account/moderation/post/unlist.blade.php @@ -0,0 +1,128 @@ +@extends('layouts.blank') + +@section('content') + +
+
+
+

Your Post Was Unlisted

+

We removed your post from public timelines because it doesn't follow our Community Guidelines.

+
+
+
+
+
+

Post Details

+ @if($interstitial->has_media) +
+
+ @if($interstitial->blurhash) + + @else + No preview available + @endif +
+
+ @if($meta->caption) +

+ Caption: {{$meta->caption}} +

+ @endif +

+ Like Count: {{$meta->likes_count}} +

+

+ Share Count: {{$meta->reblogs_count}} +

+

+ Timestamp: {{now()->parse($meta->created_at)->format('r')}} +

+

+ URL: {{$meta->url}} +

+
+
+ @else +
+
+ @if($meta->caption) +

+ Comment: {{$meta->caption}} +

+ @endif +

+ Like Count: {{$meta->likes_count}} +

+

+ Share Count: {{$meta->reblogs_count}} +

+

+ Timestamp: {{now()->parse($meta->created_at)->format('r')}} +

+

+ URL: {{$meta->url}} +

+
+
+ @endif +
+
+
+

Review the Community Guidelines

+

We want to keep {{config('app.name')}} a safe place for everyone, and we created these Community Guidelines to support and protect our community.

+
+
+ +
+ +
+
+
+ @csrf + +

Request Appeal

+

+

+ +
+

+ + + + +
+
+ +
+
+ @csrf + + + + + +
+
+
+
+ +@endsection + +@push('scripts') + +@if($interstitial->blurhash) + +@endif +@endpush \ No newline at end of file diff --git a/resources/views/admin/reports/appeals.blade.php b/resources/views/admin/reports/appeals.blade.php new file mode 100644 index 000000000..5e2c21f7e --- /dev/null +++ b/resources/views/admin/reports/appeals.blade.php @@ -0,0 +1,63 @@ +@extends('admin.partial.template-full') + +@section('section') +
+

Appeals

+ + +
+
+
+
+
+

{{App\AccountInterstitial::whereNull('appeal_handled_at')->whereNotNull('appeal_requested_at')->count()}}

+

active appeals

+
+
+ +
+
+

{{App\AccountInterstitial::whereNotNull('appeal_handled_at')->whereNotNull('appeal_requested_at')->count()}}

+

closed appeals

+
+
+
+ +
+ +@endsection \ No newline at end of file diff --git a/resources/views/admin/reports/home.blade.php b/resources/views/admin/reports/home.blade.php index 001c282e3..d78924e5b 100644 --- a/resources/views/admin/reports/home.blade.php +++ b/resources/views/admin/reports/home.blade.php @@ -15,6 +15,15 @@ +@php($ai = App\AccountInterstitial::whereNotNull('appeal_requested_at')->whereNull('appeal_handled_at')->count()) +@if($ai) + +@endif @if($reports->count())
diff --git a/resources/views/admin/reports/show_appeal.blade.php b/resources/views/admin/reports/show_appeal.blade.php new file mode 100644 index 000000000..2839dbfa5 --- /dev/null +++ b/resources/views/admin/reports/show_appeal.blade.php @@ -0,0 +1,125 @@ +@extends('admin.partial.template-full') + +@section('section') +
+
+

Moderation Appeal

+

From @{{$appeal->user->username}} about {{$appeal->appeal_requested_at->diffForHumans()}}.

+
+
+
+
+
+
+ @if($appeal->type == 'post.cw') +
+
Content Warning applied to {{$appeal->has_media ? 'Post' : 'Comment'}}
+ @if($appeal->has_media) + + @endif +
+
+ @if($meta->caption) +

+ {{$appeal->has_media ? 'Caption' : 'Comment'}}: {{$meta->caption}} +

+ @endif +

+ Like Count: {{$meta->likes_count}} +

+

+ Share Count: {{$meta->reblogs_count}} +

+

+ Timestamp: {{now()->parse($meta->created_at)->format('r')}} +

+

+ URL: {{$meta->url}} +

+

+ Message: {{$appeal->appeal_message}} +

+
+
+
+ @elseif($appeal->type == 'post.unlist') +
+
{{$appeal->has_media ? 'Post' : 'Comment'}} was unlisted from timelines
+ @if($appeal->has_media) + + @endif +
+
+ @if($meta->caption) +

+ {{$appeal->has_media ? 'Caption' : 'Comment'}}: {{$meta->caption}} +

+ @endif +

+ Like Count: {{$meta->likes_count}} +

+

+ Share Count: {{$meta->reblogs_count}} +

+

+ Timestamp: {{now()->parse($meta->created_at)->format('r')}} +

+

+ URL: {{$meta->url}} +

+

+ Message: {{$appeal->appeal_message}} +

+
+
+
+ @endif +
+
+
+ @csrf + + +
+ +
+
+ @{{$appeal->user->username}} stats +
+
+

+ Open Appeals: {{App\AccountInterstitial::whereUserId($appeal->user_id)->whereNotNull('appeal_requested_at')->whereNull('appeal_handled_at')->count()}} +

+

+ Total Appeals: {{App\AccountInterstitial::whereUserId($appeal->user_id)->whereNotNull('appeal_requested_at')->count()}} +

+

+ Total Warnings: {{App\AccountInterstitial::whereUserId($appeal->user_id)->count()}} +

+

+ Status Count: {{$appeal->user->statuses()->count()}} +

+

+ Joined: {{$appeal->user->created_at->diffForHumans(null, null, false)}} +

+
+
+
+
+@endsection + +@push('scripts') + +@endpush \ No newline at end of file diff --git a/resources/views/discover/home.blade.php b/resources/views/discover/home.blade.php index f7e27737e..a4e9f633b 100644 --- a/resources/views/discover/home.blade.php +++ b/resources/views/discover/home.blade.php @@ -8,6 +8,5 @@ @push('scripts') - @endpush \ No newline at end of file diff --git a/resources/views/profile/memory.blade.php b/resources/views/profile/memory.blade.php new file mode 100644 index 000000000..7e23972fd --- /dev/null +++ b/resources/views/profile/memory.blade.php @@ -0,0 +1,35 @@ +@extends('layouts.app',['title' => $profile->username . " on " . config('app.name')]) + +@section('content') +@if (session('error')) +
+ {{ session('error') }} +
+@endif + + +@if($profile->website) +{{$profile->website}} +@endif + + + +@endsection + +@push('meta') + @if(false == $settings['crawlable'] || $profile->remote_url) + + @else + + + @endif +@endpush + +@push('scripts') + + +@endpush diff --git a/routes/web.php b/routes/web.php index 494116b3d..432fd44c1 100644 --- a/routes/web.php +++ b/routes/web.php @@ -8,6 +8,9 @@ Route::domain(config('pixelfed.domain.admin'))->prefix('i/admin')->group(functio Route::get('reports/show/{id}', 'AdminController@showReport'); Route::post('reports/show/{id}', 'AdminController@updateReport'); Route::post('reports/bulk', 'AdminController@bulkUpdateReport'); + Route::get('reports/appeals', 'AdminController@appeals'); + Route::get('reports/appeal/{id}', 'AdminController@showAppeal'); + Route::post('reports/appeal/{id}', 'AdminController@updateAppeal'); Route::redirect('statuses', '/statuses/list'); Route::get('statuses/list', 'AdminController@statuses')->name('admin.statuses'); Route::get('statuses/show/{id}', 'AdminController@showStatus'); @@ -73,7 +76,7 @@ Route::domain(config('pixelfed.domain.admin'))->prefix('i/admin')->group(functio Route::post('newsroom/create', 'AdminController@newsroomStore'); }); -Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofactor', 'localization'])->group(function () { +Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofactor', 'localization','interstitial'])->group(function () { Route::get('/', 'SiteController@home')->name('timeline.personal'); Route::post('/', 'StatusController@store'); @@ -125,6 +128,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact Route::get('discover/tag', 'DiscoverController@getHashtags'); Route::post('status/compose', 'InternalApiController@composePost')->middleware('throttle:maxPostsPerHour,60')->middleware('throttle:maxPostsPerDay,1440'); }); + Route::group(['prefix' => 'pixelfed'], function() { Route::group(['prefix' => 'v1'], function() { Route::get('accounts/verify_credentials', 'ApiController@verifyCredentials'); @@ -146,6 +150,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact Route::get('timelines/home', 'PublicApiController@homeTimelineApi'); Route::get('newsroom/timeline', 'NewsroomController@timelineApi'); Route::post('newsroom/markasread', 'NewsroomController@markAsRead'); + Route::get('favourites', 'Api\BaseApiController@accountLikes'); }); Route::group(['prefix' => 'v2'], function() { @@ -169,6 +174,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact Route::get('discover/posts/places', 'DiscoverController@trendingPlaces'); }); }); + Route::group(['prefix' => 'local'], function () { // Route::get('accounts/verify_credentials', 'ApiController@verifyCredentials'); // Route::get('accounts/relationships', 'PublicApiController@relationships'); @@ -295,6 +301,9 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact Route::get('redirect', 'SiteController@redirectUrl'); Route::post('admin/media/block/add', 'MediaBlocklistController@add'); Route::post('admin/media/block/delete', 'MediaBlocklistController@delete'); + + Route::get('warning', 'AccountInterstitialController@get'); + Route::post('warning', 'AccountInterstitialController@read'); }); Route::group(['prefix' => 'account'], function () {