|
@@ -26,6 +26,7 @@ use App\Jobs\ImageOptimizePipeline\ImageOptimize;
|
|
use App\Jobs\LikePipeline\LikePipeline;
|
|
use App\Jobs\LikePipeline\LikePipeline;
|
|
use App\Jobs\MediaPipeline\MediaDeletePipeline;
|
|
use App\Jobs\MediaPipeline\MediaDeletePipeline;
|
|
use App\Jobs\MediaPipeline\MediaSyncLicensePipeline;
|
|
use App\Jobs\MediaPipeline\MediaSyncLicensePipeline;
|
|
|
|
+use App\Jobs\NotificationPipeline\NotificationWarmUserCache;
|
|
use App\Jobs\SharePipeline\SharePipeline;
|
|
use App\Jobs\SharePipeline\SharePipeline;
|
|
use App\Jobs\SharePipeline\UndoSharePipeline;
|
|
use App\Jobs\SharePipeline\UndoSharePipeline;
|
|
use App\Jobs\StatusPipeline\NewStatusPipeline;
|
|
use App\Jobs\StatusPipeline\NewStatusPipeline;
|
|
@@ -34,6 +35,7 @@ use App\Jobs\VideoPipeline\VideoThumbnail;
|
|
use App\Like;
|
|
use App\Like;
|
|
use App\Media;
|
|
use App\Media;
|
|
use App\Models\Conversation;
|
|
use App\Models\Conversation;
|
|
|
|
+use App\Models\CustomFilter;
|
|
use App\Notification;
|
|
use App\Notification;
|
|
use App\Profile;
|
|
use App\Profile;
|
|
use App\Services\AccountService;
|
|
use App\Services\AccountService;
|
|
@@ -137,7 +139,10 @@ class ApiV1Controller extends Controller
|
|
'redirect_uris' => 'required',
|
|
'redirect_uris' => 'required',
|
|
]);
|
|
]);
|
|
|
|
|
|
- $uris = implode(',', explode('\n', $request->redirect_uris));
|
|
|
|
|
|
+ $uris = collect(explode("\n", $request->redirect_uris))
|
|
|
|
+ ->map('urldecode')
|
|
|
|
+ ->filter()
|
|
|
|
+ ->join(',');
|
|
|
|
|
|
$client = Passport::client()->forceFill([
|
|
$client = Passport::client()->forceFill([
|
|
'user_id' => null,
|
|
'user_id' => null,
|
|
@@ -744,7 +749,7 @@ class ApiV1Controller extends Controller
|
|
} elseif ($profile['locked']) {
|
|
} elseif ($profile['locked']) {
|
|
$following = FollowerService::follows($pid, $profile['id']);
|
|
$following = FollowerService::follows($pid, $profile['id']);
|
|
if (! $following) {
|
|
if (! $following) {
|
|
- return response('', 403);
|
|
|
|
|
|
+ return response()->json([]);
|
|
}
|
|
}
|
|
$visibility = ['public', 'unlisted', 'private'];
|
|
$visibility = ['public', 'unlisted', 'private'];
|
|
} else {
|
|
} else {
|
|
@@ -760,7 +765,8 @@ class ApiV1Controller extends Controller
|
|
'reblog_of_id',
|
|
'reblog_of_id',
|
|
'type',
|
|
'type',
|
|
'id',
|
|
'id',
|
|
- 'scope'
|
|
|
|
|
|
+ 'scope',
|
|
|
|
+ 'pinned_order'
|
|
)
|
|
)
|
|
->whereProfileId($profile['id'])
|
|
->whereProfileId($profile['id'])
|
|
->whereNull('in_reply_to_id')
|
|
->whereNull('in_reply_to_id')
|
|
@@ -810,13 +816,13 @@ class ApiV1Controller extends Controller
|
|
abort_unless($request->user()->tokenCan('follow'), 403);
|
|
abort_unless($request->user()->tokenCan('follow'), 403);
|
|
|
|
|
|
$user = $request->user();
|
|
$user = $request->user();
|
|
|
|
+ abort_if($user->profile_id == $id, 400, 'Invalid profile');
|
|
|
|
+
|
|
abort_if($user->has_roles && ! UserRoleService::can('can-follow', $user->id), 403, 'Invalid permissions for this action');
|
|
abort_if($user->has_roles && ! UserRoleService::can('can-follow', $user->id), 403, 'Invalid permissions for this action');
|
|
|
|
|
|
AccountService::setLastActive($user->id);
|
|
AccountService::setLastActive($user->id);
|
|
|
|
|
|
- $target = Profile::where('id', '!=', $user->profile_id)
|
|
|
|
- ->whereNull('status')
|
|
|
|
- ->findOrFail($id);
|
|
|
|
|
|
+ $target = Profile::whereNull('status')->findOrFail($id);
|
|
|
|
|
|
abort_if($target && $target->moved_to_profile_id, 400, 'Cannot follow an account that has moved!');
|
|
abort_if($target && $target->moved_to_profile_id, 400, 'Cannot follow an account that has moved!');
|
|
|
|
|
|
@@ -861,15 +867,20 @@ class ApiV1Controller extends Controller
|
|
if ($remote == true && config('federation.activitypub.remoteFollow') == true) {
|
|
if ($remote == true && config('federation.activitypub.remoteFollow') == true) {
|
|
(new FollowerController)->sendFollow($user->profile, $target);
|
|
(new FollowerController)->sendFollow($user->profile, $target);
|
|
}
|
|
}
|
|
- } else {
|
|
|
|
- $follower = Follower::firstOrCreate([
|
|
|
|
- 'profile_id' => $user->profile_id,
|
|
|
|
|
|
+ } elseif ($remote == true) {
|
|
|
|
+ $follow = FollowRequest::firstOrCreate([
|
|
|
|
+ 'follower_id' => $user->profile_id,
|
|
'following_id' => $target->id,
|
|
'following_id' => $target->id,
|
|
]);
|
|
]);
|
|
|
|
|
|
- if ($remote == true && config('federation.activitypub.remoteFollow') == true) {
|
|
|
|
|
|
+ if (config('federation.activitypub.remoteFollow') == true) {
|
|
(new FollowerController)->sendFollow($user->profile, $target);
|
|
(new FollowerController)->sendFollow($user->profile, $target);
|
|
}
|
|
}
|
|
|
|
+ } else {
|
|
|
|
+ $follower = Follower::firstOrCreate([
|
|
|
|
+ 'profile_id' => $user->profile_id,
|
|
|
|
+ 'following_id' => $target->id,
|
|
|
|
+ ]);
|
|
FollowPipeline::dispatch($follower)->onQueue('high');
|
|
FollowPipeline::dispatch($follower)->onQueue('high');
|
|
}
|
|
}
|
|
|
|
|
|
@@ -906,10 +917,11 @@ class ApiV1Controller extends Controller
|
|
|
|
|
|
$user = $request->user();
|
|
$user = $request->user();
|
|
|
|
|
|
|
|
+ abort_if($user->profile_id == $id, 400, 'Invalid profile');
|
|
|
|
+
|
|
AccountService::setLastActive($user->id);
|
|
AccountService::setLastActive($user->id);
|
|
|
|
|
|
- $target = Profile::where('id', '!=', $user->profile_id)
|
|
|
|
- ->whereNull('status')
|
|
|
|
|
|
+ $target = Profile::whereNull('status')
|
|
->findOrFail($id);
|
|
->findOrFail($id);
|
|
|
|
|
|
$private = (bool) $target->is_private;
|
|
$private = (bool) $target->is_private;
|
|
@@ -926,6 +938,9 @@ class ApiV1Controller extends Controller
|
|
if ($followRequest) {
|
|
if ($followRequest) {
|
|
$followRequest->delete();
|
|
$followRequest->delete();
|
|
RelationshipService::refresh($target->id, $user->profile_id);
|
|
RelationshipService::refresh($target->id, $user->profile_id);
|
|
|
|
+ if ($target->domain) {
|
|
|
|
+ UnfollowPipeline::dispatch($user->profile_id, $target->id)->onQueue('high');
|
|
|
|
+ }
|
|
}
|
|
}
|
|
$resource = new Fractal\Resource\Item($target, new RelationshipTransformer);
|
|
$resource = new Fractal\Resource\Item($target, new RelationshipTransformer);
|
|
$res = $this->fractal->createData($resource)->toArray();
|
|
$res = $this->fractal->createData($resource)->toArray();
|
|
@@ -1525,7 +1540,7 @@ class ApiV1Controller extends Controller
|
|
|
|
|
|
$user = $request->user();
|
|
$user = $request->user();
|
|
|
|
|
|
- $res = FollowRequest::whereFollowingId($user->profile->id)
|
|
|
|
|
|
+ $res = FollowRequest::whereFollowingId($user->profile_id)
|
|
->limit($request->input('limit', 40))
|
|
->limit($request->input('limit', 40))
|
|
->pluck('follower_id')
|
|
->pluck('follower_id')
|
|
->map(function ($id) {
|
|
->map(function ($id) {
|
|
@@ -1717,13 +1732,14 @@ class ApiV1Controller extends Controller
|
|
'approval_required' => (bool) config_cache('instance.curated_registration.enabled'),
|
|
'approval_required' => (bool) config_cache('instance.curated_registration.enabled'),
|
|
'contact_account' => $contact,
|
|
'contact_account' => $contact,
|
|
'rules' => $rules,
|
|
'rules' => $rules,
|
|
|
|
+ 'mobile_registration' => (bool) config_cache('pixelfed.open_registration') && config('auth.in_app_registration'),
|
|
'configuration' => [
|
|
'configuration' => [
|
|
'media_attachments' => [
|
|
'media_attachments' => [
|
|
- 'image_matrix_limit' => 16777216,
|
|
|
|
|
|
+ 'image_matrix_limit' => 2073600,
|
|
'image_size_limit' => config_cache('pixelfed.max_photo_size') * 1024,
|
|
'image_size_limit' => config_cache('pixelfed.max_photo_size') * 1024,
|
|
'supported_mime_types' => explode(',', config_cache('pixelfed.media_types')),
|
|
'supported_mime_types' => explode(',', config_cache('pixelfed.media_types')),
|
|
'video_frame_rate_limit' => 120,
|
|
'video_frame_rate_limit' => 120,
|
|
- 'video_matrix_limit' => 2304000,
|
|
|
|
|
|
+ 'video_matrix_limit' => 2073600,
|
|
'video_size_limit' => config_cache('pixelfed.max_photo_size') * 1024,
|
|
'video_size_limit' => config_cache('pixelfed.max_photo_size') * 1024,
|
|
],
|
|
],
|
|
'polls' => [
|
|
'polls' => [
|
|
@@ -1893,6 +1909,8 @@ class ApiV1Controller extends Controller
|
|
switch ($media->mime) {
|
|
switch ($media->mime) {
|
|
case 'image/jpeg':
|
|
case 'image/jpeg':
|
|
case 'image/png':
|
|
case 'image/png':
|
|
|
|
+ case 'image/webp':
|
|
|
|
+ case 'image/avif':
|
|
ImageOptimize::dispatch($media)->onQueue('mmo');
|
|
ImageOptimize::dispatch($media)->onQueue('mmo');
|
|
break;
|
|
break;
|
|
|
|
|
|
@@ -2121,6 +2139,8 @@ class ApiV1Controller extends Controller
|
|
switch ($media->mime) {
|
|
switch ($media->mime) {
|
|
case 'image/jpeg':
|
|
case 'image/jpeg':
|
|
case 'image/png':
|
|
case 'image/png':
|
|
|
|
+ case 'image/webp':
|
|
|
|
+ case 'image/avif':
|
|
ImageOptimize::dispatch($media)->onQueue('mmo');
|
|
ImageOptimize::dispatch($media)->onQueue('mmo');
|
|
break;
|
|
break;
|
|
|
|
|
|
@@ -2370,7 +2390,7 @@ class ApiV1Controller extends Controller
|
|
if (empty($res)) {
|
|
if (empty($res)) {
|
|
if (! Cache::has('pf:services:notifications:hasSynced:'.$pid)) {
|
|
if (! Cache::has('pf:services:notifications:hasSynced:'.$pid)) {
|
|
Cache::put('pf:services:notifications:hasSynced:'.$pid, 1, 1209600);
|
|
Cache::put('pf:services:notifications:hasSynced:'.$pid, 1, 1209600);
|
|
- NotificationService::warmCache($pid, 400, true);
|
|
|
|
|
|
+ NotificationWarmUserCache::dispatch($pid);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -2422,6 +2442,15 @@ class ApiV1Controller extends Controller
|
|
|
|
|
|
return true;
|
|
return true;
|
|
})
|
|
})
|
|
|
|
+ ->map(function ($n) use ($pid) {
|
|
|
|
+ if (isset($n['status'])) {
|
|
|
|
+ $n['status']['favourited'] = (bool) LikeService::liked($pid, $n['status']['id']);
|
|
|
|
+ $n['status']['reblogged'] = (bool) ReblogService::get($pid, $n['status']['id']);
|
|
|
|
+ $n['status']['bookmarked'] = (bool) BookmarkService::get($pid, $n['status']['id']);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return $n;
|
|
|
|
+ })
|
|
->filter(function ($n) use ($types) {
|
|
->filter(function ($n) use ($types) {
|
|
if (! $types) {
|
|
if (! $types) {
|
|
return true;
|
|
return true;
|
|
@@ -2486,6 +2515,14 @@ class ApiV1Controller extends Controller
|
|
['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'];
|
|
['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'];
|
|
AccountService::setLastActive($request->user()->id);
|
|
AccountService::setLastActive($request->user()->id);
|
|
|
|
|
|
|
|
+ $cachedFilters = CustomFilter::getCachedFiltersForAccount($pid);
|
|
|
|
+
|
|
|
|
+ $homeFilters = array_filter($cachedFilters, function ($item) {
|
|
|
|
+ [$filter, $rules] = $item;
|
|
|
|
+
|
|
|
|
+ return in_array('home', $filter->context);
|
|
|
|
+ });
|
|
|
|
+
|
|
if (config('exp.cached_home_timeline')) {
|
|
if (config('exp.cached_home_timeline')) {
|
|
$paddedLimit = $includeReblogs ? $limit + 10 : $limit + 50;
|
|
$paddedLimit = $includeReblogs ? $limit + 10 : $limit + 50;
|
|
if ($min || $max) {
|
|
if ($min || $max) {
|
|
@@ -2522,6 +2559,23 @@ class ApiV1Controller extends Controller
|
|
->filter(function ($s) use ($includeReblogs) {
|
|
->filter(function ($s) use ($includeReblogs) {
|
|
return $includeReblogs ? true : $s['reblog'] == null;
|
|
return $includeReblogs ? true : $s['reblog'] == null;
|
|
})
|
|
})
|
|
|
|
+ ->map(function ($status) use ($homeFilters) {
|
|
|
|
+ $filterResults = CustomFilter::applyCachedFilters($homeFilters, $status);
|
|
|
|
+
|
|
|
|
+ if (! empty($filterResults)) {
|
|
|
|
+ $status['filtered'] = $filterResults;
|
|
|
|
+ $shouldHide = collect($filterResults)->contains(function ($result) {
|
|
|
|
+ return $result['filter']['filter_action'] === 'hide';
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ if ($shouldHide) {
|
|
|
|
+ return null;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return $status;
|
|
|
|
+ })
|
|
|
|
+ ->filter()
|
|
->take($limit)
|
|
->take($limit)
|
|
->map(function ($status) use ($pid) {
|
|
->map(function ($status) use ($pid) {
|
|
if ($pid) {
|
|
if ($pid) {
|
|
@@ -2546,7 +2600,7 @@ class ApiV1Controller extends Controller
|
|
$minId = null;
|
|
$minId = null;
|
|
}
|
|
}
|
|
|
|
|
|
- if ($maxId) {
|
|
|
|
|
|
+ if ($maxId && $res->count() >= $limit) {
|
|
$link = '<'.$baseUrl.'max_id='.$minId.'>; rel="next"';
|
|
$link = '<'.$baseUrl.'max_id='.$minId.'>; rel="next"';
|
|
}
|
|
}
|
|
|
|
|
|
@@ -2630,6 +2684,23 @@ class ApiV1Controller extends Controller
|
|
|
|
|
|
return $status;
|
|
return $status;
|
|
})
|
|
})
|
|
|
|
+ ->map(function ($status) use ($homeFilters) {
|
|
|
|
+ $filterResults = CustomFilter::applyCachedFilters($homeFilters, $status);
|
|
|
|
+
|
|
|
|
+ if (! empty($filterResults)) {
|
|
|
|
+ $status['filtered'] = $filterResults;
|
|
|
|
+ $shouldHide = collect($filterResults)->contains(function ($result) {
|
|
|
|
+ return $result['filter']['filter_action'] === 'hide';
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ if ($shouldHide) {
|
|
|
|
+ return null;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return $status;
|
|
|
|
+ })
|
|
|
|
+ ->filter()
|
|
->take($limit)
|
|
->take($limit)
|
|
->values();
|
|
->values();
|
|
} else {
|
|
} else {
|
|
@@ -2684,6 +2755,23 @@ class ApiV1Controller extends Controller
|
|
|
|
|
|
return $status;
|
|
return $status;
|
|
})
|
|
})
|
|
|
|
+ ->map(function ($status) use ($homeFilters) {
|
|
|
|
+ $filterResults = CustomFilter::applyCachedFilters($homeFilters, $status);
|
|
|
|
+
|
|
|
|
+ if (! empty($filterResults)) {
|
|
|
|
+ $status['filtered'] = $filterResults;
|
|
|
|
+ $shouldHide = collect($filterResults)->contains(function ($result) {
|
|
|
|
+ return $result['filter']['filter_action'] === 'hide';
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ if ($shouldHide) {
|
|
|
|
+ return null;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return $status;
|
|
|
|
+ })
|
|
|
|
+ ->filter()
|
|
->take($limit)
|
|
->take($limit)
|
|
->values();
|
|
->values();
|
|
}
|
|
}
|
|
@@ -2745,7 +2833,7 @@ class ApiV1Controller extends Controller
|
|
$limit = 40;
|
|
$limit = 40;
|
|
}
|
|
}
|
|
$user = $request->user();
|
|
$user = $request->user();
|
|
-
|
|
|
|
|
|
+ $pid = $user->profile_id;
|
|
$remote = $request->has('remote') && $request->boolean('remote');
|
|
$remote = $request->has('remote') && $request->boolean('remote');
|
|
$local = $request->boolean('local');
|
|
$local = $request->boolean('local');
|
|
$userRoleKey = $remote ? 'can-view-network-feed' : 'can-view-public-feed';
|
|
$userRoleKey = $remote ? 'can-view-network-feed' : 'can-view-public-feed';
|
|
@@ -2758,6 +2846,14 @@ class ApiV1Controller extends Controller
|
|
$hideNsfw = config('instance.hide_nsfw_on_public_feeds');
|
|
$hideNsfw = config('instance.hide_nsfw_on_public_feeds');
|
|
$amin = SnowflakeService::byDate(now()->subDays(config('federation.network_timeline_days_falloff')));
|
|
$amin = SnowflakeService::byDate(now()->subDays(config('federation.network_timeline_days_falloff')));
|
|
$asf = AdminShadowFilterService::getHideFromPublicFeedsList();
|
|
$asf = AdminShadowFilterService::getHideFromPublicFeedsList();
|
|
|
|
+
|
|
|
|
+ $cachedFilters = CustomFilter::getCachedFiltersForAccount($pid);
|
|
|
|
+
|
|
|
|
+ $homeFilters = array_filter($cachedFilters, function ($item) {
|
|
|
|
+ [$filter, $rules] = $item;
|
|
|
|
+
|
|
|
|
+ return in_array('public', $filter->context);
|
|
|
|
+ });
|
|
if ($local && $remote) {
|
|
if ($local && $remote) {
|
|
$feed = Status::select(
|
|
$feed = Status::select(
|
|
'id',
|
|
'id',
|
|
@@ -2948,6 +3044,23 @@ class ApiV1Controller extends Controller
|
|
|
|
|
|
return true;
|
|
return true;
|
|
})
|
|
})
|
|
|
|
+ ->map(function ($status) use ($homeFilters) {
|
|
|
|
+ $filterResults = CustomFilter::applyCachedFilters($homeFilters, $status);
|
|
|
|
+
|
|
|
|
+ if (! empty($filterResults)) {
|
|
|
|
+ $status['filtered'] = $filterResults;
|
|
|
|
+ $shouldHide = collect($filterResults)->contains(function ($result) {
|
|
|
|
+ return $result['filter']['filter_action'] === 'hide';
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ if ($shouldHide) {
|
|
|
|
+ return null;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return $status;
|
|
|
|
+ })
|
|
|
|
+ ->filter()
|
|
->take($limit)
|
|
->take($limit)
|
|
->values();
|
|
->values();
|
|
|
|
|
|
@@ -2969,7 +3082,7 @@ class ApiV1Controller extends Controller
|
|
$minId = null;
|
|
$minId = null;
|
|
}
|
|
}
|
|
|
|
|
|
- if ($maxId) {
|
|
|
|
|
|
+ if ($maxId && $res->count() >= $limit) {
|
|
$link = '<'.$baseUrl.'max_id='.$minId.'>; rel="next"';
|
|
$link = '<'.$baseUrl.'max_id='.$minId.'>; rel="next"';
|
|
}
|
|
}
|
|
|
|
|
|
@@ -2999,72 +3112,143 @@ class ApiV1Controller extends Controller
|
|
abort_unless($request->user()->tokenCan('read'), 403);
|
|
abort_unless($request->user()->tokenCan('read'), 403);
|
|
|
|
|
|
$this->validate($request, [
|
|
$this->validate($request, [
|
|
- 'limit' => 'min:1|max:40',
|
|
|
|
|
|
+ 'limit' => 'sometimes|integer|min:1|max:40',
|
|
'scope' => 'nullable|in:inbox,sent,requests',
|
|
'scope' => 'nullable|in:inbox,sent,requests',
|
|
|
|
+ 'min_id' => 'nullable|integer',
|
|
|
|
+ 'max_id' => 'nullable|integer',
|
|
|
|
+ 'since_id' => 'nullable|integer',
|
|
]);
|
|
]);
|
|
|
|
|
|
$limit = $request->input('limit', 20);
|
|
$limit = $request->input('limit', 20);
|
|
|
|
+ if ($limit > 20) {
|
|
|
|
+ $limit = 20;
|
|
|
|
+ }
|
|
$scope = $request->input('scope', 'inbox');
|
|
$scope = $request->input('scope', 'inbox');
|
|
$user = $request->user();
|
|
$user = $request->user();
|
|
|
|
+ $min_id = $request->input('min_id');
|
|
|
|
+ $max_id = $request->input('max_id');
|
|
|
|
+ $since_id = $request->input('since_id');
|
|
|
|
+
|
|
if ($user->has_roles && ! UserRoleService::can('can-direct-message', $user->id)) {
|
|
if ($user->has_roles && ! UserRoleService::can('can-direct-message', $user->id)) {
|
|
return [];
|
|
return [];
|
|
}
|
|
}
|
|
|
|
+
|
|
$pid = $user->profile_id;
|
|
$pid = $user->profile_id;
|
|
|
|
|
|
- if (config('database.default') == 'pgsql') {
|
|
|
|
- $dms = DirectMessage::when($scope === 'inbox', function ($q, $scope) use ($pid) {
|
|
|
|
- return $q->whereIsHidden(false)->where('to_id', $pid)->orWhere('from_id', $pid);
|
|
|
|
|
|
+ $isPgsql = config('database.default') == 'pgsql';
|
|
|
|
+
|
|
|
|
+ if ($isPgsql) {
|
|
|
|
+ $dms = DirectMessage::when($scope === 'inbox', function ($q) use ($pid) {
|
|
|
|
+ return $q->whereIsHidden(false)
|
|
|
|
+ ->where(function ($query) use ($pid) {
|
|
|
|
+ $query->where('to_id', $pid)
|
|
|
|
+ ->orWhere('from_id', $pid);
|
|
|
|
+ });
|
|
})
|
|
})
|
|
- ->when($scope === 'sent', function ($q, $scope) use ($pid) {
|
|
|
|
- return $q->whereFromId($pid)->groupBy(['to_id', 'id']);
|
|
|
|
|
|
+ ->when($scope === 'sent', function ($q) use ($pid) {
|
|
|
|
+ return $q->whereFromId($pid)
|
|
|
|
+ ->groupBy(['to_id', 'id']);
|
|
})
|
|
})
|
|
- ->when($scope === 'requests', function ($q, $scope) use ($pid) {
|
|
|
|
- return $q->whereToId($pid)->whereIsHidden(true);
|
|
|
|
|
|
+ ->when($scope === 'requests', function ($q) use ($pid) {
|
|
|
|
+ return $q->whereToId($pid)
|
|
|
|
+ ->whereIsHidden(true);
|
|
});
|
|
});
|
|
} else {
|
|
} else {
|
|
- $dms = Conversation::when($scope === 'inbox', function ($q, $scope) use ($pid) {
|
|
|
|
|
|
+ $dms = Conversation::when($scope === 'inbox', function ($q) use ($pid) {
|
|
return $q->whereIsHidden(false)
|
|
return $q->whereIsHidden(false)
|
|
- ->where('to_id', $pid)
|
|
|
|
- ->orWhere('from_id', $pid)
|
|
|
|
|
|
+ ->where(function ($query) use ($pid) {
|
|
|
|
+ $query->where('to_id', $pid)
|
|
|
|
+ ->orWhere('from_id', $pid);
|
|
|
|
+ })
|
|
->orderByDesc('status_id')
|
|
->orderByDesc('status_id')
|
|
->groupBy(['to_id', 'from_id']);
|
|
->groupBy(['to_id', 'from_id']);
|
|
})
|
|
})
|
|
- ->when($scope === 'sent', function ($q, $scope) use ($pid) {
|
|
|
|
- return $q->whereFromId($pid)->groupBy('to_id');
|
|
|
|
|
|
+ ->when($scope === 'sent', function ($q) use ($pid) {
|
|
|
|
+ return $q->whereFromId($pid)
|
|
|
|
+ ->groupBy('to_id');
|
|
})
|
|
})
|
|
- ->when($scope === 'requests', function ($q, $scope) use ($pid) {
|
|
|
|
- return $q->whereToId($pid)->whereIsHidden(true);
|
|
|
|
|
|
+ ->when($scope === 'requests', function ($q) use ($pid) {
|
|
|
|
+ return $q->whereToId($pid)
|
|
|
|
+ ->whereIsHidden(true);
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
- $dms = $dms->orderByDesc('status_id')
|
|
|
|
- ->simplePaginate($limit)
|
|
|
|
- ->map(function ($dm) use ($pid) {
|
|
|
|
- $from = $pid == $dm->to_id ? $dm->from_id : $dm->to_id;
|
|
|
|
- $res = [
|
|
|
|
- 'id' => $dm->id,
|
|
|
|
- 'unread' => false,
|
|
|
|
- 'accounts' => [
|
|
|
|
- AccountService::getMastodon($from, true),
|
|
|
|
- ],
|
|
|
|
- 'last_status' => StatusService::getDirectMessage($dm->status_id),
|
|
|
|
- ];
|
|
|
|
|
|
+ if ($min_id) {
|
|
|
|
+ $dms = $dms->where('id', '>', $min_id);
|
|
|
|
+ }
|
|
|
|
+ if ($max_id) {
|
|
|
|
+ $dms = $dms->where('id', '<', $max_id);
|
|
|
|
+ }
|
|
|
|
+ if ($since_id) {
|
|
|
|
+ $dms = $dms->where('id', '>', $since_id);
|
|
|
|
+ }
|
|
|
|
|
|
- return $res;
|
|
|
|
- })
|
|
|
|
- ->filter(function ($dm) {
|
|
|
|
- if (! $dm || empty($dm['last_status']) || ! isset($dm['accounts']) || ! count($dm['accounts']) || ! isset($dm['accounts'][0]) || ! isset($dm['accounts'][0]['id'])) {
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
|
|
+ $dms = $dms->orderByDesc('status_id')->orderBy('id');
|
|
|
|
|
|
- return true;
|
|
|
|
|
|
+ $dmResults = $dms->limit($limit + 1)->get();
|
|
|
|
+
|
|
|
|
+ $hasNextPage = $dmResults->count() > $limit;
|
|
|
|
+
|
|
|
|
+ if ($hasNextPage) {
|
|
|
|
+ $dmResults = $dmResults->take($limit);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ $transformedDms = $dmResults->map(function ($dm) use ($pid) {
|
|
|
|
+ $from = $pid == $dm->to_id ? $dm->from_id : $dm->to_id;
|
|
|
|
+
|
|
|
|
+ return [
|
|
|
|
+ 'id' => $dm->id,
|
|
|
|
+ 'unread' => false,
|
|
|
|
+ 'accounts' => [
|
|
|
|
+ AccountService::getMastodon($from, true),
|
|
|
|
+ ],
|
|
|
|
+ 'last_status' => StatusService::getDirectMessage($dm->status_id),
|
|
|
|
+ ];
|
|
|
|
+ })
|
|
|
|
+ ->filter(function ($dm) {
|
|
|
|
+ return $dm
|
|
|
|
+ && ! empty($dm['last_status'])
|
|
|
|
+ && isset($dm['accounts'])
|
|
|
|
+ && count($dm['accounts'])
|
|
|
|
+ && isset($dm['accounts'][0])
|
|
|
|
+ && isset($dm['accounts'][0]['id']);
|
|
})
|
|
})
|
|
- ->unique(function ($item, $key) {
|
|
|
|
|
|
+ ->unique(function ($item) {
|
|
return $item['accounts'][0]['id'];
|
|
return $item['accounts'][0]['id'];
|
|
})
|
|
})
|
|
->values();
|
|
->values();
|
|
|
|
|
|
- return $this->json($dms);
|
|
|
|
|
|
+ $links = [];
|
|
|
|
+
|
|
|
|
+ if (! $transformedDms->isEmpty()) {
|
|
|
|
+ $baseUrl = url()->current().'?'.http_build_query(array_merge(
|
|
|
|
+ $request->except(['min_id', 'max_id', 'since_id']),
|
|
|
|
+ ['limit' => $limit]
|
|
|
|
+ ));
|
|
|
|
+
|
|
|
|
+ $firstId = $transformedDms->first()['id'];
|
|
|
|
+ $lastId = $transformedDms->last()['id'];
|
|
|
|
+
|
|
|
|
+ $firstLink = $baseUrl;
|
|
|
|
+ $links[] = '<'.$firstLink.'>; rel="first"';
|
|
|
|
+
|
|
|
|
+ if ($hasNextPage) {
|
|
|
|
+ $nextLink = $baseUrl.'&max_id='.$lastId;
|
|
|
|
+ $links[] = '<'.$nextLink.'>; rel="next"';
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if ($max_id || $since_id) {
|
|
|
|
+ $prevLink = $baseUrl.'&min_id='.$firstId;
|
|
|
|
+ $links[] = '<'.$prevLink.'>; rel="prev"';
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (! empty($links)) {
|
|
|
|
+ return response()->json($transformedDms->toArray())
|
|
|
|
+ ->header('Link', implode(', ', $links));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return $this->json($transformedDms);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -3426,13 +3610,19 @@ class ApiV1Controller extends Controller
|
|
'in_reply_to_id' => 'nullable',
|
|
'in_reply_to_id' => 'nullable',
|
|
'media_ids' => 'sometimes|array|max:'.(int) config_cache('pixelfed.max_album_length'),
|
|
'media_ids' => 'sometimes|array|max:'.(int) config_cache('pixelfed.max_album_length'),
|
|
'sensitive' => 'nullable',
|
|
'sensitive' => 'nullable',
|
|
- 'visibility' => 'string|in:private,unlisted,public',
|
|
|
|
|
|
+ 'visibility' => 'string|in:private,unlisted,public,direct',
|
|
'spoiler_text' => 'sometimes|max:140',
|
|
'spoiler_text' => 'sometimes|max:140',
|
|
'place_id' => 'sometimes|integer|min:1|max:128769',
|
|
'place_id' => 'sometimes|integer|min:1|max:128769',
|
|
'collection_ids' => 'sometimes|array|max:3',
|
|
'collection_ids' => 'sometimes|array|max:3',
|
|
'comments_disabled' => 'sometimes|boolean',
|
|
'comments_disabled' => 'sometimes|boolean',
|
|
]);
|
|
]);
|
|
|
|
|
|
|
|
+ if ($request->filled('visibility') && $request->input('visibility') === 'direct') {
|
|
|
|
+ return $this->json([
|
|
|
|
+ 'error' => 'Direct visibility is not available.',
|
|
|
|
+ ], 400);
|
|
|
|
+ }
|
|
|
|
+
|
|
if ($request->hasHeader('idempotency-key')) {
|
|
if ($request->hasHeader('idempotency-key')) {
|
|
$key = 'pf:api:v1:status:idempotency-key:'.$request->user()->id.':'.hash('sha1', $request->header('idempotency-key'));
|
|
$key = 'pf:api:v1:status:idempotency-key:'.$request->user()->id.':'.hash('sha1', $request->header('idempotency-key'));
|
|
$exists = Cache::has($key);
|
|
$exists = Cache::has($key);
|
|
@@ -3494,7 +3684,7 @@ class ApiV1Controller extends Controller
|
|
return [];
|
|
return [];
|
|
}
|
|
}
|
|
|
|
|
|
- $defaultCaption = config_cache('database.default') === 'mysql' ? null : "";
|
|
|
|
|
|
+ $defaultCaption = '';
|
|
$content = $request->filled('status') ? strip_tags($request->input('status')) : $defaultCaption;
|
|
$content = $request->filled('status') ? strip_tags($request->input('status')) : $defaultCaption;
|
|
$cw = $user->profile->cw == true ? true : $request->boolean('sensitive', false);
|
|
$cw = $user->profile->cw == true ? true : $request->boolean('sensitive', false);
|
|
$spoilerText = $cw && $request->filled('spoiler_text') ? $request->input('spoiler_text') : null;
|
|
$spoilerText = $cw && $request->filled('spoiler_text') ? $request->input('spoiler_text') : null;
|
|
@@ -3687,7 +3877,7 @@ class ApiV1Controller extends Controller
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- $defaultCaption = config_cache('database.default') === 'mysql' ? null : "";
|
|
|
|
|
|
+ $defaultCaption = config_cache('database.default') === 'mysql' ? null : '';
|
|
$share = Status::firstOrCreate([
|
|
$share = Status::firstOrCreate([
|
|
'caption' => $defaultCaption,
|
|
'caption' => $defaultCaption,
|
|
'rendered' => $defaultCaption,
|
|
'rendered' => $defaultCaption,
|
|
@@ -3814,8 +4004,16 @@ class ApiV1Controller extends Controller
|
|
$pe = $request->has(self::PF_API_ENTITY_KEY);
|
|
$pe = $request->has(self::PF_API_ENTITY_KEY);
|
|
$pid = $request->user()->profile_id;
|
|
$pid = $request->user()->profile_id;
|
|
|
|
|
|
|
|
+ $cachedFilters = CustomFilter::getCachedFiltersForAccount($pid);
|
|
|
|
+
|
|
|
|
+ $tagFilters = array_filter($cachedFilters, function ($item) {
|
|
|
|
+ [$filter, $rules] = $item;
|
|
|
|
+
|
|
|
|
+ return in_array('tags', $filter->context);
|
|
|
|
+ });
|
|
|
|
+
|
|
if ($min || $max) {
|
|
if ($min || $max) {
|
|
- $minMax = SnowflakeService::byDate(now()->subMonths(6));
|
|
|
|
|
|
+ $minMax = SnowflakeService::byDate(now()->subMonths(9));
|
|
if ($min && intval($min) < $minMax) {
|
|
if ($min && intval($min) < $minMax) {
|
|
return [];
|
|
return [];
|
|
}
|
|
}
|
|
@@ -3870,6 +4068,23 @@ class ApiV1Controller extends Controller
|
|
|
|
|
|
return ! in_array($i['account']['id'], $filters) && ! in_array($domain, $domainBlocks);
|
|
return ! in_array($i['account']['id'], $filters) && ! in_array($domain, $domainBlocks);
|
|
})
|
|
})
|
|
|
|
+ ->map(function ($status) use ($tagFilters) {
|
|
|
|
+ $filterResults = CustomFilter::applyCachedFilters($tagFilters, $status);
|
|
|
|
+
|
|
|
|
+ if (! empty($filterResults)) {
|
|
|
|
+ $status['filtered'] = $filterResults;
|
|
|
|
+ $shouldHide = collect($filterResults)->contains(function ($result) {
|
|
|
|
+ return $result['filter']['filter_action'] === 'hide';
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ if ($shouldHide) {
|
|
|
|
+ return null;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return $status;
|
|
|
|
+ })
|
|
|
|
+ ->filter()
|
|
->take($limit)
|
|
->take($limit)
|
|
->values()
|
|
->values()
|
|
->toArray();
|
|
->toArray();
|
|
@@ -4347,4 +4562,101 @@ class ApiV1Controller extends Controller
|
|
})
|
|
})
|
|
);
|
|
);
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ public function accountRemoveFollowById(Request $request, $id)
|
|
|
|
+ {
|
|
|
|
+ abort_if(! $request->user(), 403);
|
|
|
|
+
|
|
|
|
+ $pid = $request->user()->profile_id;
|
|
|
|
+
|
|
|
|
+ if ($pid === $id) {
|
|
|
|
+ return $this->json(['error' => 'Request invalid! target_id is same user id.'], 500);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ $exists = Follower::whereProfileId($id)
|
|
|
|
+ ->whereFollowingId($pid)
|
|
|
|
+ ->first();
|
|
|
|
+
|
|
|
|
+ abort_unless($exists, 404);
|
|
|
|
+
|
|
|
|
+ $exists->delete();
|
|
|
|
+
|
|
|
|
+ RelationshipService::refresh($pid, $id);
|
|
|
|
+ RelationshipService::refresh($pid, $id);
|
|
|
|
+
|
|
|
|
+ UnfollowPipeline::dispatch($id, $pid)->onQueue('high');
|
|
|
|
+
|
|
|
|
+ Cache::forget('profile:following:'.$id);
|
|
|
|
+ Cache::forget('profile:followers:'.$id);
|
|
|
|
+ Cache::forget('profile:following:'.$pid);
|
|
|
|
+ Cache::forget('profile:followers:'.$pid);
|
|
|
|
+ Cache::forget('api:local:exp:rec:'.$pid);
|
|
|
|
+ Cache::forget('user:account:id:'.$id);
|
|
|
|
+ Cache::forget('user:account:id:'.$pid);
|
|
|
|
+ Cache::forget('profile:follower_count:'.$id);
|
|
|
|
+ Cache::forget('profile:follower_count:'.$pid);
|
|
|
|
+ Cache::forget('profile:following_count:'.$id);
|
|
|
|
+ Cache::forget('profile:following_count:'.$pid);
|
|
|
|
+ AccountService::del($pid);
|
|
|
|
+ AccountService::del($id);
|
|
|
|
+
|
|
|
|
+ $res = RelationshipService::get($id, $pid);
|
|
|
|
+ return $this->json($res);
|
|
|
|
+ }
|
|
|
|
+ /**
|
|
|
|
+ * GET /api/v1/statuses/{id}/pin
|
|
|
|
+ */
|
|
|
|
+ public function statusPin(Request $request, $id)
|
|
|
|
+ {
|
|
|
|
+ abort_if(! $request->user(), 403);
|
|
|
|
+ abort_unless($request->user()->tokenCan('write'), 403);
|
|
|
|
+ $user = $request->user();
|
|
|
|
+ $status = Status::whereScope('public')->find($id);
|
|
|
|
+
|
|
|
|
+ if (! $status) {
|
|
|
|
+ return $this->json(['error' => 'Record not found'], 404);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if ($status->profile_id != $user->profile_id) {
|
|
|
|
+ return $this->json(['error' => "Validation failed: Someone else's post cannot be pinned"], 422);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ $res = StatusService::markPin($status->id);
|
|
|
|
+
|
|
|
|
+ if (! $res['success']) {
|
|
|
|
+ return $this->json([
|
|
|
|
+ 'error' => $res['error'],
|
|
|
|
+ ], 422);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ $statusRes = StatusService::get($status->id, true, true);
|
|
|
|
+ $status['pinned'] = true;
|
|
|
|
+
|
|
|
|
+ return $this->json($statusRes);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * GET /api/v1/statuses/{id}/unpin
|
|
|
|
+ */
|
|
|
|
+ public function statusUnpin(Request $request, $id)
|
|
|
|
+ {
|
|
|
|
+ abort_if(! $request->user(), 403);
|
|
|
|
+ abort_unless($request->user()->tokenCan('write'), 403);
|
|
|
|
+ $status = Status::whereScope('public')->findOrFail($id);
|
|
|
|
+ $user = $request->user();
|
|
|
|
+
|
|
|
|
+ if ($status->profile_id != $user->profile_id) {
|
|
|
|
+ return $this->json(['error' => 'Record not found'], 404);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ $res = StatusService::unmarkPin($status->id);
|
|
|
|
+ if (! $res) {
|
|
|
|
+ return $this->json($res, 422);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ $status = StatusService::get($status->id, true, true);
|
|
|
|
+ $status['pinned'] = false;
|
|
|
|
+
|
|
|
|
+ return $this->json($status);
|
|
|
|
+ }
|
|
}
|
|
}
|