mirror of
https://github.com/pixelfed/pixelfed.git
synced 2025-02-02 18:00:47 +00:00
Merge branch 'dev' into 2-translate-help-center-pages
This commit is contained in:
commit
6662118d4a
259 changed files with 11601 additions and 7171 deletions
|
@ -1270,7 +1270,7 @@ DOCKER_WORKER_HEALTHCHECK_INTERVAL="${DOCKER_ALL_DEFAULT_HEALTHCHECK_INTERVAL:?e
|
||||||
#
|
#
|
||||||
# @see https://hub.docker.com/r/nginxproxy/nginx-proxy
|
# @see https://hub.docker.com/r/nginxproxy/nginx-proxy
|
||||||
# @dottie/validate required
|
# @dottie/validate required
|
||||||
DOCKER_PROXY_VERSION="1.4"
|
DOCKER_PROXY_VERSION="1.6"
|
||||||
|
|
||||||
# How often Docker health check should run for [proxy] service
|
# How often Docker health check should run for [proxy] service
|
||||||
# @dottie/validate required
|
# @dottie/validate required
|
||||||
|
|
33
CHANGELOG.md
33
CHANGELOG.md
|
@ -1,6 +1,39 @@
|
||||||
# Release Notes
|
# Release Notes
|
||||||
|
|
||||||
## [Unreleased](https://github.com/pixelfed/pixelfed/compare/v0.12.3...dev)
|
## [Unreleased](https://github.com/pixelfed/pixelfed/compare/v0.12.3...dev)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
- WebGL photo filters ([#5374](https://github.com/pixelfed/pixelfed/pull/5374))
|
||||||
|
|
||||||
|
### OAuth
|
||||||
|
- Fix oauth oob (urn:ietf:wg:oauth:2.0:oob) support. ([8afbdb03](https://github.com/pixelfed/pixelfed/commit/8afbdb03))
|
||||||
|
|
||||||
|
### Updates
|
||||||
|
- Update AP helpers, reject statuses with invalid dates ([960f3849](https://github.com/pixelfed/pixelfed/commit/960f3849))
|
||||||
|
- Update DirectMessage API, fix broken threading ([044d410c](https://github.com/pixelfed/pixelfed/commit/044d410c))
|
||||||
|
- Update Status caption render logic ([fb8dbb95](https://github.com/pixelfed/pixelfed/commit/fb8dbb95))
|
||||||
|
- Update ApiV1Controller, fix bookmark bug. Closes #5216 ([9f7cc52c](https://github.com/pixelfed/pixelfed/commit/9f7cc52c))
|
||||||
|
- Update Status caption logic, stop storing duplicate html caption in db and defer to cached StatusService rendering ([9eeb7b67](https://github.com/pixelfed/pixelfed/commit/9eeb7b67))
|
||||||
|
- Update AutolinkService, optimize lookups ([eac2c196](https://github.com/pixelfed/pixelfed/commit/eac2c196))
|
||||||
|
- Update DirectMessageController, remove 72h limit for admins ([639df410](https://github.com/pixelfed/pixelfed/commit/639df410))
|
||||||
|
- Update StatusService, fix newlines ([56c07b7a](https://github.com/pixelfed/pixelfed/commit/56c07b7a))
|
||||||
|
- Update confirm email template, add plaintext link. Fixes #5375 ([45986707](https://github.com/pixelfed/pixelfed/commit/45986707))
|
||||||
|
- Update UserVerifyEmail command ([77da9ad8](https://github.com/pixelfed/pixelfed/commit/77da9ad8))
|
||||||
|
- Update StatusStatelessTransformer, refactor the caption field to be compliant with the MastoAPI. Fixes #5364 ([79039ba5](https://github.com/pixelfed/pixelfed/commit/79039ba5))
|
||||||
|
- Update mailgun config, add endpoint and scheme ([271d5114](https://github.com/pixelfed/pixelfed/commit/271d5114))
|
||||||
|
- Update search and status logic to fix postgres bugs ([8c39ef4](https://github.com/pixelfed/pixelfed/commit/8c39ef4))
|
||||||
|
- Update db, fix sqlite migrations ([#5379](https://github.com/pixelfed/pixelfed/pull/5379))
|
||||||
|
- Update CatchUnoptimizedMedia command, make 1hr limit opt-in ([99b15b73](https://github.com/pixelfed/pixelfed/commit/99b15b73))
|
||||||
|
- Update IG, fix Instagram import. Closes #5411 ([fd434aec](https://github.com/pixelfed/pixelfed/commit/fd434aec))
|
||||||
|
- Update StatusTagsPipeline, fix hashtag bug and formatting ([d516b799](https://github.com/pixelfed/pixelfed/commit/d516b799))
|
||||||
|
- Update CollectionController, fix showCollection signature ([4e1dd599](https://github.com/pixelfed/pixelfed/commit/4e1dd599))
|
||||||
|
- Update ApiV1Dot1Controller, fix in-app registration ([56f17b99](https://github.com/pixelfed/pixelfed/commit/56f17b99))
|
||||||
|
- Update VerifyCsrfToken middleware, add oauth token. Fixes #5426 ([79ebbc2d](https://github.com/pixelfed/pixelfed/commit/79ebbc2d))
|
||||||
|
- Update AdminSettingsController, increase max photo size limit from 50MB to 1GB ([aa448354](https://github.com/pixelfed/pixelfed/commit/aa448354))
|
||||||
|
- Update BearerTokenResponse, return scopes in /oauth/token endpoint. Fixes #5286 ([d8f5c302](https://github.com/pixelfed/pixelfed/commit/d8f5c302))
|
||||||
|
- Update hashtag component, fix missing video thumbnails ([witten](https://github.com/witten)) ([#5427](https://github.com/pixelfed/pixelfed/pull/5427))
|
||||||
|
- Update AP Status Transformer, fix inReplyTo. Fixes #5409 ([83cc932f](https://github.com/pixelfed/pixelfed/commit/83cc932f))
|
||||||
|
- Update Data Export, refactor following/follower and statuses exports to allow accounts of any size with api entity instead of ap ([0d25917c](https://github.com/pixelfed/pixelfed/commit/0d25917c))
|
||||||
- ([](https://github.com/pixelfed/pixelfed/commit/))
|
- ([](https://github.com/pixelfed/pixelfed/commit/))
|
||||||
|
|
||||||
## [v0.12.4 (2024-11-08)](https://github.com/pixelfed/pixelfed/compare/v0.12.4...dev)
|
## [v0.12.4 (2024-11-08)](https://github.com/pixelfed/pixelfed/compare/v0.12.4...dev)
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
<a href="https://circleci.com/gh/pixelfed/pixelfed"><img src="https://circleci.com/gh/pixelfed/pixelfed.svg?style=svg" alt="Build Status"></a>
|
<a href="https://circleci.com/gh/pixelfed/pixelfed"><img src="https://circleci.com/gh/pixelfed/pixelfed.svg?style=svg" alt="Build Status"></a>
|
||||||
<a href="https://packagist.org/packages/pixelfed/pixelfed"><img src="https://poser.pugx.org/pixelfed/pixelfed/v/stable.svg" alt="Latest Stable Version"></a>
|
<a href="https://packagist.org/packages/pixelfed/pixelfed"><img src="https://poser.pugx.org/pixelfed/pixelfed/v/stable.svg" alt="Latest Stable Version"></a>
|
||||||
<a href="https://packagist.org/packages/pixelfed/pixelfed"><img src="https://poser.pugx.org/pixelfed/pixelfed/license.svg" alt="License"></a>
|
<a href="https://packagist.org/packages/pixelfed/pixelfed"><img src="https://poser.pugx.org/pixelfed/pixelfed/license.svg" alt="License"></a>
|
||||||
|
<a title="Crowdin" target="_blank" href="https://crowdin.com/project/pixelfed"><img src="https://badges.crowdin.net/pixelfed/localized.svg"></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
|
@ -11,13 +11,13 @@ class BearerTokenResponse extends \League\OAuth2\Server\ResponseTypes\BearerToke
|
||||||
* AuthorizationServer::getResponseType() to pull in your version of
|
* AuthorizationServer::getResponseType() to pull in your version of
|
||||||
* this class rather than the default.
|
* this class rather than the default.
|
||||||
*
|
*
|
||||||
* @param AccessTokenEntityInterface $accessToken
|
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
protected function getExtraParams(AccessTokenEntityInterface $accessToken)
|
protected function getExtraParams(AccessTokenEntityInterface $accessToken)
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
'scope' => implode(' ', array_map(fn ($scope) => $scope->getIdentifier(), $accessToken->getScopes())),
|
||||||
'created_at' => time(),
|
'created_at' => time(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,10 +40,11 @@ class CatchUnoptimizedMedia extends Command
|
||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
|
$hasLimit = (bool) config('media.image_optimize.catch_unoptimized_media_hour_limit');
|
||||||
Media::whereNull('processed_at')
|
Media::whereNull('processed_at')
|
||||||
->where('created_at', '>', now()->subHours(1))
|
->when($hasLimit, function($q, $hasLimit) {
|
||||||
->where('skip_optimize', '!=', true)
|
$q->where('created_at', '>', now()->subHours(1));
|
||||||
->whereNull('remote_url')
|
})->whereNull('remote_url')
|
||||||
->whereNotNull('status_id')
|
->whereNotNull('status_id')
|
||||||
->whereNotNull('media_path')
|
->whereNotNull('media_path')
|
||||||
->whereIn('mime', [
|
->whereIn('mime', [
|
||||||
|
@ -52,6 +53,7 @@ class CatchUnoptimizedMedia extends Command
|
||||||
])
|
])
|
||||||
->chunk(50, function($medias) {
|
->chunk(50, function($medias) {
|
||||||
foreach ($medias as $media) {
|
foreach ($medias as $media) {
|
||||||
|
if ($media->skip_optimize) continue;
|
||||||
ImageOptimize::dispatch($media);
|
ImageOptimize::dispatch($media);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
85
app/Console/Commands/ReclaimUsername.php
Normal file
85
app/Console/Commands/ReclaimUsername.php
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\User;
|
||||||
|
use App\Profile;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use function Laravel\Prompts\search;
|
||||||
|
use function Laravel\Prompts\text;
|
||||||
|
use function Laravel\Prompts\confirm;
|
||||||
|
|
||||||
|
class ReclaimUsername extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'app:reclaim-username';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Force delete a user and their profile to reclaim a username';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$username = search(
|
||||||
|
label: 'What username would you like to reclaim?',
|
||||||
|
options: fn (string $search) => strlen($search) > 0 ? $this->getUsernameOptions($search) : [],
|
||||||
|
required: true
|
||||||
|
);
|
||||||
|
|
||||||
|
$user = User::whereUsername($username)->withTrashed()->first();
|
||||||
|
$profile = Profile::whereUsername($username)->withTrashed()->first();
|
||||||
|
|
||||||
|
if (!$user && !$profile) {
|
||||||
|
$this->error("No user or profile found with username: {$username}");
|
||||||
|
return Command::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($user->delete_after === null || $user->status !== 'deleted') {
|
||||||
|
$this->error("Cannot reclaim an active account: {$username}");
|
||||||
|
return Command::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$confirm = confirm(
|
||||||
|
label: "Are you sure you want to force delete user and profile with username: {$username}?",
|
||||||
|
default: false
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!$confirm) {
|
||||||
|
$this->info('Operation cancelled.');
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($user) {
|
||||||
|
$user->forceDelete();
|
||||||
|
$this->info("User {$username} has been force deleted.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($profile) {
|
||||||
|
$profile->forceDelete();
|
||||||
|
$this->info("Profile {$username} has been force deleted.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->info('Username reclaimed successfully!');
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getUsernameOptions(string $search = ''): array
|
||||||
|
{
|
||||||
|
return User::where('username', 'like', "{$search}%")
|
||||||
|
->withTrashed()
|
||||||
|
->whereNotNull('delete_after')
|
||||||
|
->take(10)
|
||||||
|
->pluck('username')
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,17 +2,16 @@
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
use Illuminate\Console\Command;
|
|
||||||
use App\Models\ImportPost;
|
|
||||||
use App\Services\ImportService;
|
|
||||||
use App\Media;
|
use App\Media;
|
||||||
|
use App\Models\ImportPost;
|
||||||
use App\Profile;
|
use App\Profile;
|
||||||
use App\Status;
|
|
||||||
use Storage;
|
|
||||||
use App\Services\AccountService;
|
use App\Services\AccountService;
|
||||||
|
use App\Services\ImportService;
|
||||||
use App\Services\MediaPathService;
|
use App\Services\MediaPathService;
|
||||||
|
use App\Status;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use App\Util\Lexer\Autolink;
|
use Storage;
|
||||||
|
|
||||||
class TransformImports extends Command
|
class TransformImports extends Command
|
||||||
{
|
{
|
||||||
|
@ -52,6 +51,7 @@ class TransformImports extends Command
|
||||||
if (! $profile) {
|
if (! $profile) {
|
||||||
$ip->skip_missing_media = true;
|
$ip->skip_missing_media = true;
|
||||||
$ip->save();
|
$ip->save();
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,6 +66,7 @@ class TransformImports extends Command
|
||||||
if ($exists == true) {
|
if ($exists == true) {
|
||||||
$ip->skip_missing_media = true;
|
$ip->skip_missing_media = true;
|
||||||
$ip->save();
|
$ip->save();
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,6 +74,7 @@ class TransformImports extends Command
|
||||||
if (! $idk) {
|
if (! $idk) {
|
||||||
$ip->skip_missing_media = true;
|
$ip->skip_missing_media = true;
|
||||||
$ip->save();
|
$ip->save();
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,6 +83,7 @@ class TransformImports extends Command
|
||||||
ImportService::getPostCount($profile->id, true);
|
ImportService::getPostCount($profile->id, true);
|
||||||
$ip->skip_missing_media = true;
|
$ip->skip_missing_media = true;
|
||||||
$ip->save();
|
$ip->save();
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,6 +99,7 @@ class TransformImports extends Command
|
||||||
if ($missingMedia === true) {
|
if ($missingMedia === true) {
|
||||||
$ip->skip_missing_media = true;
|
$ip->skip_missing_media = true;
|
||||||
$ip->save();
|
$ip->save();
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,7 +107,6 @@ class TransformImports extends Command
|
||||||
$status = new Status;
|
$status = new Status;
|
||||||
$status->profile_id = $pid;
|
$status->profile_id = $pid;
|
||||||
$status->caption = $caption;
|
$status->caption = $caption;
|
||||||
$status->rendered = strlen(trim($caption)) ? Autolink::create()->autolink($ip->caption) : null;
|
|
||||||
$status->type = $ip->post_type;
|
$status->type = $ip->post_type;
|
||||||
|
|
||||||
$status->scope = 'unlisted';
|
$status->scope = 'unlisted';
|
||||||
|
@ -120,6 +123,7 @@ class TransformImports extends Command
|
||||||
if (! Storage::exists($og)) {
|
if (! Storage::exists($og)) {
|
||||||
$ip->skip_missing_media = true;
|
$ip->skip_missing_media = true;
|
||||||
$ip->save();
|
$ip->save();
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$size = Storage::size($og);
|
$size = Storage::size($og);
|
||||||
|
|
|
@ -5,8 +5,9 @@ namespace App\Console\Commands;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use App\User;
|
use App\User;
|
||||||
|
use Illuminate\Contracts\Console\PromptsForMissingInput;
|
||||||
|
|
||||||
class UserVerifyEmail extends Command
|
class UserVerifyEmail extends Command implements PromptsForMissingInput
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The name and signature of the console command.
|
* The name and signature of the console command.
|
||||||
|
@ -39,13 +40,19 @@ class UserVerifyEmail extends Command
|
||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
$user = User::whereUsername($this->argument('username'))->first();
|
$username = $this->argument('username');
|
||||||
|
$user = User::whereUsername($username)->first();
|
||||||
|
|
||||||
if(!$user) {
|
if(!$user) {
|
||||||
$this->error('Username not found');
|
$this->error('Username not found');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if($user->email_verified_at) {
|
||||||
|
$this->error('Email already verified ' . $user->email_verified_at->diffForHumans());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$user->email_verified_at = now();
|
$user->email_verified_at = now();
|
||||||
$user->save();
|
$user->save();
|
||||||
$this->info('Successfully verified email address for ' . $user->username);
|
$this->info('Successfully verified email address for ' . $user->username);
|
||||||
|
|
|
@ -600,7 +600,7 @@ trait AdminSettingsController
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'image_quality' => 'required|integer|min:1|max:100',
|
'image_quality' => 'required|integer|min:1|max:100',
|
||||||
'max_album_length' => 'required|integer|min:1|max:20',
|
'max_album_length' => 'required|integer|min:1|max:20',
|
||||||
'max_photo_size' => 'required|integer|min:100|max:50000',
|
'max_photo_size' => 'required|integer|min:100|max:1000000',
|
||||||
'media_types' => 'required',
|
'media_types' => 'required',
|
||||||
'optimize_image' => 'required',
|
'optimize_image' => 'required',
|
||||||
'optimize_video' => 'required',
|
'optimize_video' => 'required',
|
||||||
|
|
|
@ -137,7 +137,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,
|
||||||
|
@ -1426,6 +1429,8 @@ class ApiV1Controller extends Controller
|
||||||
|
|
||||||
$status['favourited'] = true;
|
$status['favourited'] = true;
|
||||||
$status['favourites_count'] = $status['favourites_count'] + 1;
|
$status['favourites_count'] = $status['favourites_count'] + 1;
|
||||||
|
$status['bookmarked'] = BookmarkService::get($user->profile_id, $status['id']);
|
||||||
|
$status['reblogged'] = ReblogService::get($user->profile_id, $status['id']);
|
||||||
|
|
||||||
return $this->json($status);
|
return $this->json($status);
|
||||||
}
|
}
|
||||||
|
@ -1484,6 +1489,8 @@ class ApiV1Controller extends Controller
|
||||||
|
|
||||||
$status['favourited'] = false;
|
$status['favourited'] = false;
|
||||||
$status['favourites_count'] = isset($ogStatus) ? $ogStatus->likes_count : $status['favourites_count'] - 1;
|
$status['favourites_count'] = isset($ogStatus) ? $ogStatus->likes_count : $status['favourites_count'] - 1;
|
||||||
|
$status['bookmarked'] = BookmarkService::get($user->profile_id, $status['id']);
|
||||||
|
$status['reblogged'] = ReblogService::get($user->profile_id, $status['id']);
|
||||||
|
|
||||||
return $this->json($status);
|
return $this->json($status);
|
||||||
}
|
}
|
||||||
|
@ -1878,7 +1885,7 @@ class ApiV1Controller extends Controller
|
||||||
$media->original_sha256 = $hash;
|
$media->original_sha256 = $hash;
|
||||||
$media->size = $photo->getSize();
|
$media->size = $photo->getSize();
|
||||||
$media->mime = $mime;
|
$media->mime = $mime;
|
||||||
$media->caption = $request->input('description') ?? "";
|
$media->caption = $request->input('description') ?? '';
|
||||||
$media->filter_class = $filterClass;
|
$media->filter_class = $filterClass;
|
||||||
$media->filter_name = $filterName;
|
$media->filter_name = $filterName;
|
||||||
if ($license) {
|
if ($license) {
|
||||||
|
@ -2106,7 +2113,7 @@ class ApiV1Controller extends Controller
|
||||||
$media->original_sha256 = $hash;
|
$media->original_sha256 = $hash;
|
||||||
$media->size = $photo->getSize();
|
$media->size = $photo->getSize();
|
||||||
$media->mime = $mime;
|
$media->mime = $mime;
|
||||||
$media->caption = $request->input('description') ?? "";
|
$media->caption = $request->input('description') ?? '';
|
||||||
$media->filter_class = $filterClass;
|
$media->filter_class = $filterClass;
|
||||||
$media->filter_name = $filterName;
|
$media->filter_name = $filterName;
|
||||||
if ($license) {
|
if ($license) {
|
||||||
|
@ -3490,8 +3497,8 @@ class ApiV1Controller extends Controller
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
$content = strip_tags($request->input('status'));
|
$defaultCaption = "";
|
||||||
$rendered = Autolink::create()->autolink($content);
|
$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;
|
||||||
|
|
||||||
|
@ -3505,7 +3512,7 @@ class ApiV1Controller extends Controller
|
||||||
|
|
||||||
$status = new Status;
|
$status = new Status;
|
||||||
$status->caption = $content;
|
$status->caption = $content;
|
||||||
$status->rendered = $rendered;
|
$status->rendered = $defaultCaption;
|
||||||
$status->scope = $visibility;
|
$status->scope = $visibility;
|
||||||
$status->visibility = $visibility;
|
$status->visibility = $visibility;
|
||||||
$status->profile_id = $user->profile_id;
|
$status->profile_id = $user->profile_id;
|
||||||
|
@ -3530,7 +3537,7 @@ class ApiV1Controller extends Controller
|
||||||
if (! $in_reply_to_id) {
|
if (! $in_reply_to_id) {
|
||||||
$status = new Status;
|
$status = new Status;
|
||||||
$status->caption = $content;
|
$status->caption = $content;
|
||||||
$status->rendered = $rendered;
|
$status->rendered = $defaultCaption;
|
||||||
$status->profile_id = $user->profile_id;
|
$status->profile_id = $user->profile_id;
|
||||||
$status->is_nsfw = $cw;
|
$status->is_nsfw = $cw;
|
||||||
$status->cw_summary = $spoilerText;
|
$status->cw_summary = $spoilerText;
|
||||||
|
@ -3683,7 +3690,10 @@ class ApiV1Controller extends Controller
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$defaultCaption = config_cache('database.default') === 'mysql' ? null : '';
|
||||||
$share = Status::firstOrCreate([
|
$share = Status::firstOrCreate([
|
||||||
|
'caption' => $defaultCaption,
|
||||||
|
'rendered' => $defaultCaption,
|
||||||
'profile_id' => $user->profile_id,
|
'profile_id' => $user->profile_id,
|
||||||
'reblog_of_id' => $status->id,
|
'reblog_of_id' => $status->id,
|
||||||
'type' => 'share',
|
'type' => 'share',
|
||||||
|
@ -3698,6 +3708,8 @@ class ApiV1Controller extends Controller
|
||||||
ReblogService::add($user->profile_id, $status->id);
|
ReblogService::add($user->profile_id, $status->id);
|
||||||
$res = StatusService::getMastodon($status->id);
|
$res = StatusService::getMastodon($status->id);
|
||||||
$res['reblogged'] = true;
|
$res['reblogged'] = true;
|
||||||
|
$res['favourited'] = LikeService::liked($user->profile_id, $status->id);
|
||||||
|
$res['bookmarked'] = BookmarkService::get($user->profile_id, $status->id);
|
||||||
|
|
||||||
return $this->json($res);
|
return $this->json($res);
|
||||||
}
|
}
|
||||||
|
@ -3744,6 +3756,8 @@ class ApiV1Controller extends Controller
|
||||||
|
|
||||||
$res = StatusService::getMastodon($status->id);
|
$res = StatusService::getMastodon($status->id);
|
||||||
$res['reblogged'] = false;
|
$res['reblogged'] = false;
|
||||||
|
$res['favourited'] = LikeService::liked($user->profile_id, $status->id);
|
||||||
|
$res['bookmarked'] = BookmarkService::get($user->profile_id, $status->id);
|
||||||
|
|
||||||
return $this->json($res);
|
return $this->json($res);
|
||||||
}
|
}
|
||||||
|
@ -3951,6 +3965,7 @@ class ApiV1Controller extends Controller
|
||||||
abort_unless($request->user()->tokenCan('write'), 403);
|
abort_unless($request->user()->tokenCan('write'), 403);
|
||||||
|
|
||||||
$status = Status::findOrFail($id);
|
$status = Status::findOrFail($id);
|
||||||
|
$user = $request->user();
|
||||||
$pid = $request->user()->profile_id;
|
$pid = $request->user()->profile_id;
|
||||||
$account = AccountService::get($status->profile_id);
|
$account = AccountService::get($status->profile_id);
|
||||||
abort_if(isset($account['moved'], $account['moved']['id']), 422, 'Cannot bookmark a post from an account that has migrated');
|
abort_if(isset($account['moved'], $account['moved']['id']), 422, 'Cannot bookmark a post from an account that has migrated');
|
||||||
|
@ -3994,6 +4009,7 @@ class ApiV1Controller extends Controller
|
||||||
|
|
||||||
$status = Status::findOrFail($id);
|
$status = Status::findOrFail($id);
|
||||||
$pid = $request->user()->profile_id;
|
$pid = $request->user()->profile_id;
|
||||||
|
$user = $request->user();
|
||||||
|
|
||||||
abort_if($user->has_roles && ! UserRoleService::can('can-bookmark', $user->id), 403, 'Invalid permissions for this action');
|
abort_if($user->has_roles && ! UserRoleService::can('can-bookmark', $user->id), 403, 'Invalid permissions for this action');
|
||||||
abort_if($status->in_reply_to_id || $status->reblog_of_id, 404);
|
abort_if($status->in_reply_to_id || $status->reblog_of_id, 404);
|
||||||
|
|
|
@ -37,7 +37,6 @@ use App\Status;
|
||||||
use App\StatusArchived;
|
use App\StatusArchived;
|
||||||
use App\User;
|
use App\User;
|
||||||
use App\UserSetting;
|
use App\UserSetting;
|
||||||
use App\Util\Lexer\Autolink;
|
|
||||||
use App\Util\Lexer\RestrictedNames;
|
use App\Util\Lexer\RestrictedNames;
|
||||||
use Cache;
|
use Cache;
|
||||||
use DB;
|
use DB;
|
||||||
|
@ -49,6 +48,7 @@ use Jenssegers\Agent\Agent;
|
||||||
use League\Fractal;
|
use League\Fractal;
|
||||||
use League\Fractal\Serializer\ArraySerializer;
|
use League\Fractal\Serializer\ArraySerializer;
|
||||||
use Mail;
|
use Mail;
|
||||||
|
use Purify;
|
||||||
|
|
||||||
class ApiV1Dot1Controller extends Controller
|
class ApiV1Dot1Controller extends Controller
|
||||||
{
|
{
|
||||||
|
@ -629,9 +629,6 @@ class ApiV1Dot1Controller extends Controller
|
||||||
abort_if(BouncerService::checkIp($request->ip()), 404);
|
abort_if(BouncerService::checkIp($request->ip()), 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
$rl = RateLimiter::attempt('pf:apiv1.1:iarc:'.$request->ip(), config('pixelfed.app_registration_confirm_rate_limit_attempts', 20), function () {}, config('pixelfed.app_registration_confirm_rate_limit_decay', 1800));
|
|
||||||
abort_if(! $rl, 429, 'Too many requests');
|
|
||||||
|
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'user_token' => 'required',
|
'user_token' => 'required',
|
||||||
'random_token' => 'required',
|
'random_token' => 'required',
|
||||||
|
@ -658,7 +655,7 @@ class ApiV1Dot1Controller extends Controller
|
||||||
$user->last_active_at = now();
|
$user->last_active_at = now();
|
||||||
$user->save();
|
$user->save();
|
||||||
|
|
||||||
$token = $user->createToken('Pixelfed', ['read', 'write', 'follow', 'admin:read', 'admin:write', 'push']);
|
$token = $user->createToken('Pixelfed', ['read', 'write', 'follow', 'push']);
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'access_token' => $token->accessToken,
|
'access_token' => $token->accessToken,
|
||||||
|
@ -1292,15 +1289,14 @@ class ApiV1Dot1Controller extends Controller
|
||||||
if ($user->last_active_at == null) {
|
if ($user->last_active_at == null) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
$defaultCaption = '';
|
||||||
$content = strip_tags($request->input('status'));
|
$content = $request->filled('status') ? strip_tags(Purify::clean($request->input('status'))) : $defaultCaption;
|
||||||
$rendered = Autolink::create()->autolink($content);
|
|
||||||
$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;
|
||||||
|
|
||||||
$status = new Status;
|
$status = new Status;
|
||||||
$status->caption = $content;
|
$status->caption = $content;
|
||||||
$status->rendered = $rendered;
|
$status->rendered = $defaultCaption;
|
||||||
$status->profile_id = $user->profile_id;
|
$status->profile_id = $user->profile_id;
|
||||||
$status->is_nsfw = $cw;
|
$status->is_nsfw = $cw;
|
||||||
$status->cw_summary = $spoilerText;
|
$status->cw_summary = $spoilerText;
|
||||||
|
|
|
@ -29,7 +29,7 @@ class CollectionController extends Controller
|
||||||
return view('collection.create', compact('collection'));
|
return view('collection.create', compact('collection'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function show(Request $request, int $id)
|
public function show(Request $request, $id)
|
||||||
{
|
{
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
$collection = CollectionService::getCollection($id);
|
$collection = CollectionService::getCollection($id);
|
||||||
|
|
|
@ -8,12 +8,12 @@ use App\Services\StatusService;
|
||||||
use App\Status;
|
use App\Status;
|
||||||
use App\Transformer\Api\StatusTransformer;
|
use App\Transformer\Api\StatusTransformer;
|
||||||
use App\UserFilter;
|
use App\UserFilter;
|
||||||
use App\Util\Lexer\Autolink;
|
|
||||||
use Auth;
|
use Auth;
|
||||||
use DB;
|
use DB;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use League\Fractal;
|
use League\Fractal;
|
||||||
use League\Fractal\Serializer\ArraySerializer;
|
use League\Fractal\Serializer\ArraySerializer;
|
||||||
|
use Purify;
|
||||||
|
|
||||||
class CommentController extends Controller
|
class CommentController extends Controller
|
||||||
{
|
{
|
||||||
|
@ -56,12 +56,11 @@ class CommentController extends Controller
|
||||||
|
|
||||||
$reply = DB::transaction(function () use ($comment, $status, $profile, $nsfw) {
|
$reply = DB::transaction(function () use ($comment, $status, $profile, $nsfw) {
|
||||||
$scope = $profile->is_private == true ? 'private' : 'public';
|
$scope = $profile->is_private == true ? 'private' : 'public';
|
||||||
$autolink = Autolink::create()->autolink($comment);
|
$reply = new Status;
|
||||||
$reply = new Status();
|
|
||||||
$reply->profile_id = $profile->id;
|
$reply->profile_id = $profile->id;
|
||||||
$reply->is_nsfw = $nsfw;
|
$reply->is_nsfw = $nsfw;
|
||||||
$reply->caption = e($comment);
|
$reply->caption = Purify::clean($comment);
|
||||||
$reply->rendered = $autolink;
|
$reply->rendered = "";
|
||||||
$reply->in_reply_to_id = $status->id;
|
$reply->in_reply_to_id = $status->id;
|
||||||
$reply->in_reply_to_profile_id = $status->profile_id;
|
$reply->in_reply_to_profile_id = $status->profile_id;
|
||||||
$reply->scope = $scope;
|
$reply->scope = $scope;
|
||||||
|
@ -76,9 +75,9 @@ class CommentController extends Controller
|
||||||
CommentPipeline::dispatch($status, $reply);
|
CommentPipeline::dispatch($status, $reply);
|
||||||
|
|
||||||
if ($request->ajax()) {
|
if ($request->ajax()) {
|
||||||
$fractal = new Fractal\Manager();
|
$fractal = new Fractal\Manager;
|
||||||
$fractal->setSerializer(new ArraySerializer());
|
$fractal->setSerializer(new ArraySerializer);
|
||||||
$entity = new Fractal\Resource\Item($reply, new StatusTransformer());
|
$entity = new Fractal\Resource\Item($reply, new StatusTransformer);
|
||||||
$entity = $fractal->createData($entity)->toArray();
|
$entity = $fractal->createData($entity)->toArray();
|
||||||
$response = [
|
$response = [
|
||||||
'code' => 200,
|
'code' => 200,
|
||||||
|
|
|
@ -25,12 +25,12 @@ use App\Services\UserStorageService;
|
||||||
use App\Status;
|
use App\Status;
|
||||||
use App\Transformer\Api\MediaTransformer;
|
use App\Transformer\Api\MediaTransformer;
|
||||||
use App\UserFilter;
|
use App\UserFilter;
|
||||||
use App\Util\Lexer\Autolink;
|
|
||||||
use App\Util\Media\Filter;
|
use App\Util\Media\Filter;
|
||||||
use App\Util\Media\License;
|
use App\Util\Media\License;
|
||||||
use Auth;
|
use Auth;
|
||||||
use Cache;
|
use Cache;
|
||||||
use DB;
|
use DB;
|
||||||
|
use Purify;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use League\Fractal;
|
use League\Fractal;
|
||||||
|
@ -43,8 +43,8 @@ class ComposeController extends Controller
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->middleware('auth');
|
$this->middleware('auth');
|
||||||
$this->fractal = new Fractal\Manager();
|
$this->fractal = new Fractal\Manager;
|
||||||
$this->fractal->setSerializer(new ArraySerializer());
|
$this->fractal->setSerializer(new ArraySerializer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function show(Request $request)
|
public function show(Request $request)
|
||||||
|
@ -112,14 +112,14 @@ class ComposeController extends Controller
|
||||||
|
|
||||||
abort_if(MediaBlocklistService::exists($hash) == true, 451);
|
abort_if(MediaBlocklistService::exists($hash) == true, 451);
|
||||||
|
|
||||||
$media = new Media();
|
$media = new Media;
|
||||||
$media->status_id = null;
|
$media->status_id = null;
|
||||||
$media->profile_id = $profile->id;
|
$media->profile_id = $profile->id;
|
||||||
$media->user_id = $user->id;
|
$media->user_id = $user->id;
|
||||||
$media->media_path = $path;
|
$media->media_path = $path;
|
||||||
$media->original_sha256 = $hash;
|
$media->original_sha256 = $hash;
|
||||||
$media->size = $photo->getSize();
|
$media->size = $photo->getSize();
|
||||||
$media->caption = "";
|
$media->caption = '';
|
||||||
$media->mime = $mime;
|
$media->mime = $mime;
|
||||||
$media->filter_class = $filterClass;
|
$media->filter_class = $filterClass;
|
||||||
$media->filter_name = $filterName;
|
$media->filter_name = $filterName;
|
||||||
|
@ -151,7 +151,7 @@ class ComposeController extends Controller
|
||||||
$user->save();
|
$user->save();
|
||||||
|
|
||||||
Cache::forget($limitKey);
|
Cache::forget($limitKey);
|
||||||
$resource = new Fractal\Resource\Item($media, new MediaTransformer());
|
$resource = new Fractal\Resource\Item($media, new MediaTransformer);
|
||||||
$res = $this->fractal->createData($resource)->toArray();
|
$res = $this->fractal->createData($resource)->toArray();
|
||||||
$res['preview_url'] = $preview_url;
|
$res['preview_url'] = $preview_url;
|
||||||
$res['url'] = $url;
|
$res['url'] = $url;
|
||||||
|
@ -570,8 +570,9 @@ class ComposeController extends Controller
|
||||||
$status->cw_summary = $request->input('spoiler_text');
|
$status->cw_summary = $request->input('spoiler_text');
|
||||||
}
|
}
|
||||||
|
|
||||||
$status->caption = strip_tags($request->caption);
|
$defaultCaption = "";
|
||||||
$status->rendered = Autolink::create()->autolink($status->caption);
|
$status->caption = strip_tags($request->input('caption')) ?? $defaultCaption;
|
||||||
|
$status->rendered = $defaultCaption;
|
||||||
$status->scope = 'draft';
|
$status->scope = 'draft';
|
||||||
$status->visibility = 'draft';
|
$status->visibility = 'draft';
|
||||||
$status->profile_id = $profile->id;
|
$status->profile_id = $profile->id;
|
||||||
|
@ -675,6 +676,7 @@ class ComposeController extends Controller
|
||||||
$place = $request->input('place');
|
$place = $request->input('place');
|
||||||
$cw = $request->input('cw');
|
$cw = $request->input('cw');
|
||||||
$tagged = $request->input('tagged');
|
$tagged = $request->input('tagged');
|
||||||
|
$defaultCaption = config_cache('database.default') === 'mysql' ? null : "";
|
||||||
|
|
||||||
if ($place && is_array($place)) {
|
if ($place && is_array($place)) {
|
||||||
$status->place_id = $place['id'];
|
$status->place_id = $place['id'];
|
||||||
|
@ -684,7 +686,8 @@ class ComposeController extends Controller
|
||||||
$status->comments_disabled = (bool) $request->input('comments_disabled');
|
$status->comments_disabled = (bool) $request->input('comments_disabled');
|
||||||
}
|
}
|
||||||
|
|
||||||
$status->caption = strip_tags($request->caption);
|
$status->caption = $request->filled('caption') ? strip_tags($request->caption) : $defaultCaption;
|
||||||
|
$status->rendered = $defaultCaption;
|
||||||
$status->profile_id = $profile->id;
|
$status->profile_id = $profile->id;
|
||||||
$entities = [];
|
$entities = [];
|
||||||
$visibility = $profile->unlisted == true && $visibility == 'public' ? 'unlisted' : $visibility;
|
$visibility = $profile->unlisted == true && $visibility == 'public' ? 'unlisted' : $visibility;
|
||||||
|
@ -693,7 +696,6 @@ class ComposeController extends Controller
|
||||||
$status->visibility = $visibility;
|
$status->visibility = $visibility;
|
||||||
$status->scope = $visibility;
|
$status->scope = $visibility;
|
||||||
$status->type = 'text';
|
$status->type = 'text';
|
||||||
$status->rendered = Autolink::create()->autolink($status->caption);
|
|
||||||
$status->entities = json_encode(array_merge([
|
$status->entities = json_encode(array_merge([
|
||||||
'timg' => [
|
'timg' => [
|
||||||
'version' => 0,
|
'version' => 0,
|
||||||
|
@ -806,7 +808,6 @@ class ComposeController extends Controller
|
||||||
$status = new Status;
|
$status = new Status;
|
||||||
$status->profile_id = $request->user()->profile_id;
|
$status->profile_id = $request->user()->profile_id;
|
||||||
$status->caption = $request->input('caption');
|
$status->caption = $request->input('caption');
|
||||||
$status->rendered = Autolink::create()->autolink($status->caption);
|
|
||||||
$status->visibility = 'draft';
|
$status->visibility = 'draft';
|
||||||
$status->scope = 'draft';
|
$status->scope = 'draft';
|
||||||
$status->type = 'poll';
|
$status->type = 'poll';
|
||||||
|
|
|
@ -22,6 +22,7 @@ use App\Services\WebfingerService;
|
||||||
use App\Status;
|
use App\Status;
|
||||||
use App\UserFilter;
|
use App\UserFilter;
|
||||||
use App\Util\ActivityPub\Helpers;
|
use App\Util\ActivityPub\Helpers;
|
||||||
|
use App\Util\Lexer\Autolink;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
@ -306,7 +307,9 @@ class DirectMessageController extends Controller
|
||||||
|
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
abort_if($user->has_roles && ! UserRoleService::can('can-direct-message', $user->id), 403, 'Invalid permissions for this action');
|
abort_if($user->has_roles && ! UserRoleService::can('can-direct-message', $user->id), 403, 'Invalid permissions for this action');
|
||||||
|
if (! $user->is_admin) {
|
||||||
abort_if($user->created_at->gt(now()->subHours(72)), 400, 'You need to wait a bit before you can DM another account');
|
abort_if($user->created_at->gt(now()->subHours(72)), 400, 'You need to wait a bit before you can DM another account');
|
||||||
|
}
|
||||||
$profile = $user->profile;
|
$profile = $user->profile;
|
||||||
$recipient = Profile::where('id', '!=', $profile->id)->findOrFail($request->input('to_id'));
|
$recipient = Profile::where('id', '!=', $profile->id)->findOrFail($request->input('to_id'));
|
||||||
|
|
||||||
|
@ -326,7 +329,6 @@ class DirectMessageController extends Controller
|
||||||
$status = new Status;
|
$status = new Status;
|
||||||
$status->profile_id = $profile->id;
|
$status->profile_id = $profile->id;
|
||||||
$status->caption = $msg;
|
$status->caption = $msg;
|
||||||
$status->rendered = $msg;
|
|
||||||
$status->visibility = 'direct';
|
$status->visibility = 'direct';
|
||||||
$status->scope = 'direct';
|
$status->scope = 'direct';
|
||||||
$status->in_reply_to_profile_id = $recipient->id;
|
$status->in_reply_to_profile_id = $recipient->id;
|
||||||
|
@ -372,7 +374,7 @@ class DirectMessageController extends Controller
|
||||||
->exists();
|
->exists();
|
||||||
|
|
||||||
if ($recipient->domain == null && $hidden == false && ! $nf) {
|
if ($recipient->domain == null && $hidden == false && ! $nf) {
|
||||||
$notification = new Notification();
|
$notification = new Notification;
|
||||||
$notification->profile_id = $recipient->id;
|
$notification->profile_id = $recipient->id;
|
||||||
$notification->actor_id = $profile->id;
|
$notification->actor_id = $profile->id;
|
||||||
$notification->action = 'dm';
|
$notification->action = 'dm';
|
||||||
|
@ -405,6 +407,8 @@ class DirectMessageController extends Controller
|
||||||
{
|
{
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'pid' => 'required',
|
'pid' => 'required',
|
||||||
|
'max_id' => 'sometimes|integer',
|
||||||
|
'min_id' => 'sometimes|integer',
|
||||||
]);
|
]);
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
abort_if($user->has_roles && ! UserRoleService::can('can-direct-message', $user->id), 403, 'Invalid permissions for this action');
|
abort_if($user->has_roles && ! UserRoleService::can('can-direct-message', $user->id), 403, 'Invalid permissions for this action');
|
||||||
|
@ -419,29 +423,33 @@ class DirectMessageController extends Controller
|
||||||
if ($min_id) {
|
if ($min_id) {
|
||||||
$res = DirectMessage::select('*')
|
$res = DirectMessage::select('*')
|
||||||
->where('id', '>', $min_id)
|
->where('id', '>', $min_id)
|
||||||
->where(function ($q) use ($pid, $uid) {
|
->where(function ($query) use ($pid, $uid) {
|
||||||
return $q->where([['from_id', $pid], ['to_id', $uid],
|
$query->where('from_id', $pid)->where('to_id', $uid);
|
||||||
])->orWhere([['from_id', $uid], ['to_id', $pid]]);
|
})->orWhere(function ($query) use ($pid, $uid) {
|
||||||
|
$query->where('from_id', $uid)->where('to_id', $pid);
|
||||||
})
|
})
|
||||||
->latest()
|
->orderBy('id', 'asc')
|
||||||
->take(8)
|
->take(8)
|
||||||
->get();
|
->get()
|
||||||
|
->reverse();
|
||||||
} elseif ($max_id) {
|
} elseif ($max_id) {
|
||||||
$res = DirectMessage::select('*')
|
$res = DirectMessage::select('*')
|
||||||
->where('id', '<', $max_id)
|
->where('id', '<', $max_id)
|
||||||
->where(function ($q) use ($pid, $uid) {
|
->where(function ($query) use ($pid, $uid) {
|
||||||
return $q->where([['from_id', $pid], ['to_id', $uid],
|
$query->where('from_id', $pid)->where('to_id', $uid);
|
||||||
])->orWhere([['from_id', $uid], ['to_id', $pid]]);
|
})->orWhere(function ($query) use ($pid, $uid) {
|
||||||
|
$query->where('from_id', $uid)->where('to_id', $pid);
|
||||||
})
|
})
|
||||||
->latest()
|
->orderBy('id', 'desc')
|
||||||
->take(8)
|
->take(8)
|
||||||
->get();
|
->get();
|
||||||
} else {
|
} else {
|
||||||
$res = DirectMessage::where(function ($q) use ($pid, $uid) {
|
$res = DirectMessage::where(function ($query) use ($pid, $uid) {
|
||||||
return $q->where([['from_id', $pid], ['to_id', $uid],
|
$query->where('from_id', $pid)->where('to_id', $uid);
|
||||||
])->orWhere([['from_id', $uid], ['to_id', $pid]]);
|
})->orWhere(function ($query) use ($pid, $uid) {
|
||||||
|
$query->where('from_id', $uid)->where('to_id', $pid);
|
||||||
})
|
})
|
||||||
->latest()
|
->orderBy('id', 'desc')
|
||||||
->take(8)
|
->take(8)
|
||||||
->get();
|
->get();
|
||||||
}
|
}
|
||||||
|
@ -630,13 +638,12 @@ class DirectMessageController extends Controller
|
||||||
$status = new Status;
|
$status = new Status;
|
||||||
$status->profile_id = $profile->id;
|
$status->profile_id = $profile->id;
|
||||||
$status->caption = null;
|
$status->caption = null;
|
||||||
$status->rendered = null;
|
|
||||||
$status->visibility = 'direct';
|
$status->visibility = 'direct';
|
||||||
$status->scope = 'direct';
|
$status->scope = 'direct';
|
||||||
$status->in_reply_to_profile_id = $recipient->id;
|
$status->in_reply_to_profile_id = $recipient->id;
|
||||||
$status->save();
|
$status->save();
|
||||||
|
|
||||||
$media = new Media();
|
$media = new Media;
|
||||||
$media->status_id = $status->id;
|
$media->status_id = $status->id;
|
||||||
$media->profile_id = $profile->id;
|
$media->profile_id = $profile->id;
|
||||||
$media->user_id = $user->id;
|
$media->user_id = $user->id;
|
||||||
|
@ -824,6 +831,11 @@ class DirectMessageController extends Controller
|
||||||
{
|
{
|
||||||
$profile = $dm->author;
|
$profile = $dm->author;
|
||||||
$url = $dm->recipient->sharedInbox ?? $dm->recipient->inbox_url;
|
$url = $dm->recipient->sharedInbox ?? $dm->recipient->inbox_url;
|
||||||
|
$status = $dm->status;
|
||||||
|
|
||||||
|
if (! $status) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$tags = [
|
$tags = [
|
||||||
[
|
[
|
||||||
|
@ -833,6 +845,8 @@ class DirectMessageController extends Controller
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$content = $status->caption ? Autolink::create()->autolink($status->caption) : null;
|
||||||
|
|
||||||
$body = [
|
$body = [
|
||||||
'@context' => [
|
'@context' => [
|
||||||
'https://w3id.org/security/v1',
|
'https://w3id.org/security/v1',
|
||||||
|
@ -848,7 +862,7 @@ class DirectMessageController extends Controller
|
||||||
'id' => $dm->status->url(),
|
'id' => $dm->status->url(),
|
||||||
'type' => 'Note',
|
'type' => 'Note',
|
||||||
'summary' => null,
|
'summary' => null,
|
||||||
'content' => $dm->status->rendered ?? $dm->status->caption,
|
'content' => $content,
|
||||||
'inReplyTo' => null,
|
'inReplyTo' => null,
|
||||||
'published' => $dm->status->created_at->toAtomString(),
|
'published' => $dm->status->created_at->toAtomString(),
|
||||||
'url' => $dm->status->url(),
|
'url' => $dm->status->url(),
|
||||||
|
|
|
@ -2,13 +2,14 @@
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Support\Facades\Cache;
|
|
||||||
use App\Models\Group;
|
use App\Models\Group;
|
||||||
use App\Models\GroupPost;
|
use App\Models\GroupPost;
|
||||||
use App\Status;
|
|
||||||
use App\Models\InstanceActor;
|
use App\Models\InstanceActor;
|
||||||
use App\Services\MediaService;
|
use App\Services\MediaService;
|
||||||
|
use App\Status;
|
||||||
|
use App\Util\Lexer\Autolink;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
|
||||||
class GroupFederationController extends Controller
|
class GroupFederationController extends Controller
|
||||||
{
|
{
|
||||||
|
@ -16,6 +17,7 @@ class GroupFederationController extends Controller
|
||||||
{
|
{
|
||||||
$group = Group::whereLocal(true)->whereActivitypub(true)->findOrFail($id);
|
$group = Group::whereLocal(true)->whereActivitypub(true)->findOrFail($id);
|
||||||
$res = $this->showGroupObject($group);
|
$res = $this->showGroupObject($group);
|
||||||
|
|
||||||
return response()->json($res, 200, [], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
return response()->json($res, 200, [], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +34,7 @@ class GroupFederationController extends Controller
|
||||||
'type' => 'Group',
|
'type' => 'Group',
|
||||||
'attributedTo' => [
|
'attributedTo' => [
|
||||||
'type' => 'Person',
|
'type' => 'Person',
|
||||||
'id' => $group->admin->permalink()
|
'id' => $group->admin->permalink(),
|
||||||
],
|
],
|
||||||
// 'endpoints' => [
|
// 'endpoints' => [
|
||||||
// 'sharedInbox' => config('app.url') . '/f/inbox'
|
// 'sharedInbox' => config('app.url') . '/f/inbox'
|
||||||
|
@ -43,23 +45,24 @@ class GroupFederationController extends Controller
|
||||||
'owner' => $group->permalink(),
|
'owner' => $group->permalink(),
|
||||||
'publicKeyPem' => InstanceActor::first()->public_key,
|
'publicKeyPem' => InstanceActor::first()->public_key,
|
||||||
],
|
],
|
||||||
'url' => $group->permalink()
|
'url' => $group->permalink(),
|
||||||
];
|
];
|
||||||
|
|
||||||
if ($group->metadata && isset($group->metadata['avatar'])) {
|
if ($group->metadata && isset($group->metadata['avatar'])) {
|
||||||
$res['icon'] = [
|
$res['icon'] = [
|
||||||
'type' => 'Image',
|
'type' => 'Image',
|
||||||
'url' => $group->metadata['avatar']['url']
|
'url' => $group->metadata['avatar']['url'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($group->metadata && isset($group->metadata['header'])) {
|
if ($group->metadata && isset($group->metadata['header'])) {
|
||||||
$res['image'] = [
|
$res['image'] = [
|
||||||
'type' => 'Image',
|
'type' => 'Image',
|
||||||
'url' => $group->metadata['header']['url']
|
'url' => $group->metadata['header']['url'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
ksort($res);
|
ksort($res);
|
||||||
|
|
||||||
return $res;
|
return $res;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -70,7 +73,7 @@ class GroupFederationController extends Controller
|
||||||
$gp = GroupPost::whereGroupId($gid)->findOrFail($sid);
|
$gp = GroupPost::whereGroupId($gid)->findOrFail($sid);
|
||||||
$status = Status::findOrFail($gp->status_id);
|
$status = Status::findOrFail($gp->status_id);
|
||||||
// permission check
|
// permission check
|
||||||
|
$content = $status->caption ? Autolink::create()->autolink($status->caption) : null;
|
||||||
$res = [
|
$res = [
|
||||||
'@context' => 'https://www.w3.org/ns/activitystreams',
|
'@context' => 'https://www.w3.org/ns/activitystreams',
|
||||||
'id' => $gp->url(),
|
'id' => $gp->url(),
|
||||||
|
@ -78,7 +81,7 @@ class GroupFederationController extends Controller
|
||||||
'type' => 'Note',
|
'type' => 'Note',
|
||||||
|
|
||||||
'summary' => null,
|
'summary' => null,
|
||||||
'content' => $status->rendered ?? $status->caption,
|
'content' => $content,
|
||||||
'inReplyTo' => null,
|
'inReplyTo' => null,
|
||||||
|
|
||||||
'published' => $status->created_at->toAtomString(),
|
'published' => $status->created_at->toAtomString(),
|
||||||
|
@ -94,9 +97,10 @@ class GroupFederationController extends Controller
|
||||||
'target' => [
|
'target' => [
|
||||||
'type' => 'Collection',
|
'type' => 'Collection',
|
||||||
'id' => $group->permalink('/wall'),
|
'id' => $group->permalink('/wall'),
|
||||||
'attributedTo' => $group->permalink()
|
'attributedTo' => $group->permalink(),
|
||||||
]
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
// ksort($res);
|
// ksort($res);
|
||||||
return response()->json($res, 200, [], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
return response()->json($res, 200, [], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,48 +2,29 @@
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
use App\AccountInterstitial;
|
||||||
use App\{
|
use App\Bookmark;
|
||||||
AccountInterstitial,
|
use App\DirectMessage;
|
||||||
Bookmark,
|
use App\DiscoverCategory;
|
||||||
DirectMessage,
|
use App\Follower;
|
||||||
DiscoverCategory,
|
|
||||||
Hashtag,
|
|
||||||
Follower,
|
|
||||||
Like,
|
|
||||||
Media,
|
|
||||||
MediaTag,
|
|
||||||
Notification,
|
|
||||||
Profile,
|
|
||||||
StatusHashtag,
|
|
||||||
Status,
|
|
||||||
User,
|
|
||||||
UserFilter,
|
|
||||||
};
|
|
||||||
use Auth,Cache;
|
|
||||||
use Illuminate\Support\Facades\Redis;
|
|
||||||
use Carbon\Carbon;
|
|
||||||
use League\Fractal;
|
|
||||||
use App\Transformer\Api\{
|
|
||||||
AccountTransformer,
|
|
||||||
StatusTransformer,
|
|
||||||
// StatusMediaContainerTransformer,
|
|
||||||
};
|
|
||||||
use App\Util\Media\Filter;
|
|
||||||
use App\Jobs\StatusPipeline\NewStatusPipeline;
|
|
||||||
use App\Jobs\ModPipeline\HandleSpammerPipeline;
|
use App\Jobs\ModPipeline\HandleSpammerPipeline;
|
||||||
use League\Fractal\Serializer\ArraySerializer;
|
use App\Profile;
|
||||||
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
|
use App\Services\BookmarkService;
|
||||||
use Illuminate\Validation\Rule;
|
use App\Services\DiscoverService;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use App\Services\MediaTagService;
|
|
||||||
use App\Services\ModLogService;
|
use App\Services\ModLogService;
|
||||||
use App\Services\PublicTimelineService;
|
use App\Services\PublicTimelineService;
|
||||||
use App\Services\SnowflakeService;
|
|
||||||
use App\Services\StatusService;
|
use App\Services\StatusService;
|
||||||
use App\Services\UserFilterService;
|
use App\Services\UserFilterService;
|
||||||
use App\Services\DiscoverService;
|
use App\Status; // StatusMediaContainerTransformer,
|
||||||
use App\Services\BookmarkService;
|
use App\Transformer\Api\StatusTransformer;
|
||||||
|
use App\User;
|
||||||
|
use Auth;
|
||||||
|
use Cache;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Redis;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
use League\Fractal;
|
||||||
|
use League\Fractal\Serializer\ArraySerializer;
|
||||||
|
|
||||||
class InternalApiController extends Controller
|
class InternalApiController extends Controller
|
||||||
{
|
{
|
||||||
|
@ -52,8 +33,8 @@ class InternalApiController extends Controller
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->middleware('auth');
|
$this->middleware('auth');
|
||||||
$this->fractal = new Fractal\Manager();
|
$this->fractal = new Fractal\Manager;
|
||||||
$this->fractal->setSerializer(new ArraySerializer());
|
$this->fractal->setSerializer(new ArraySerializer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// deprecated v2 compose api
|
// deprecated v2 compose api
|
||||||
|
@ -63,10 +44,7 @@ class InternalApiController extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
// deprecated
|
// deprecated
|
||||||
public function discover(Request $request)
|
public function discover(Request $request) {}
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function discoverPosts(Request $request)
|
public function discoverPosts(Request $request)
|
||||||
{
|
{
|
||||||
|
@ -84,6 +62,7 @@ class InternalApiController extends Controller
|
||||||
})
|
})
|
||||||
->take(12)
|
->take(12)
|
||||||
->values();
|
->values();
|
||||||
|
|
||||||
return response()->json(compact('posts'));
|
return response()->json(compact('posts'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,7 +89,7 @@ class InternalApiController extends Controller
|
||||||
public function statusReplies(Request $request, int $id)
|
public function statusReplies(Request $request, int $id)
|
||||||
{
|
{
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'limit' => 'nullable|int|min:1|max:6'
|
'limit' => 'nullable|int|min:1|max:6',
|
||||||
]);
|
]);
|
||||||
$parent = Status::whereScope('public')->findOrFail($id);
|
$parent = Status::whereScope('public')->findOrFail($id);
|
||||||
$limit = $request->input('limit') ?? 3;
|
$limit = $request->input('limit') ?? 3;
|
||||||
|
@ -118,16 +97,13 @@ class InternalApiController extends Controller
|
||||||
->orderBy('created_at', 'desc')
|
->orderBy('created_at', 'desc')
|
||||||
->take($limit)
|
->take($limit)
|
||||||
->get();
|
->get();
|
||||||
$resource = new Fractal\Resource\Collection($children, new StatusTransformer());
|
$resource = new Fractal\Resource\Collection($children, new StatusTransformer);
|
||||||
$res = $this->fractal->createData($resource)->toArray();
|
$res = $this->fractal->createData($resource)->toArray();
|
||||||
|
|
||||||
return response()->json($res);
|
return response()->json($res);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function stories(Request $request)
|
public function stories(Request $request) {}
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public function discoverCategories(Request $request)
|
public function discoverCategories(Request $request)
|
||||||
{
|
{
|
||||||
|
@ -136,9 +112,10 @@ class InternalApiController extends Controller
|
||||||
return [
|
return [
|
||||||
'name' => $item->name,
|
'name' => $item->name,
|
||||||
'url' => $item->url(),
|
'url' => $item->url(),
|
||||||
'thumb' => $item->thumb()
|
'thumb' => $item->thumb(),
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
return response()->json($res);
|
return response()->json($res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,15 +130,15 @@ class InternalApiController extends Controller
|
||||||
'addcw',
|
'addcw',
|
||||||
'remcw',
|
'remcw',
|
||||||
'unlist',
|
'unlist',
|
||||||
'spammer'
|
'spammer',
|
||||||
])
|
]),
|
||||||
],
|
],
|
||||||
'item_id' => 'required|integer|min:1',
|
'item_id' => 'required|integer|min:1',
|
||||||
'item_type' => [
|
'item_type' => [
|
||||||
'required',
|
'required',
|
||||||
'string',
|
'string',
|
||||||
Rule::in(['profile', 'status'])
|
Rule::in(['profile', 'status']),
|
||||||
]
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$action = $request->input('action');
|
$action = $request->input('action');
|
||||||
|
@ -184,7 +161,7 @@ class InternalApiController extends Controller
|
||||||
->action('admin.status.moderate')
|
->action('admin.status.moderate')
|
||||||
->metadata([
|
->metadata([
|
||||||
'action' => 'cw',
|
'action' => 'cw',
|
||||||
'message' => 'Success!'
|
'message' => 'Success!',
|
||||||
])
|
])
|
||||||
->accessLevel('admin')
|
->accessLevel('admin')
|
||||||
->save();
|
->save();
|
||||||
|
@ -229,7 +206,7 @@ class InternalApiController extends Controller
|
||||||
->action('admin.status.moderate')
|
->action('admin.status.moderate')
|
||||||
->metadata([
|
->metadata([
|
||||||
'action' => 'remove_cw',
|
'action' => 'remove_cw',
|
||||||
'message' => 'Success!'
|
'message' => 'Success!',
|
||||||
])
|
])
|
||||||
->accessLevel('admin')
|
->accessLevel('admin')
|
||||||
->save();
|
->save();
|
||||||
|
@ -255,7 +232,7 @@ class InternalApiController extends Controller
|
||||||
->action('admin.status.moderate')
|
->action('admin.status.moderate')
|
||||||
->metadata([
|
->metadata([
|
||||||
'action' => 'unlist',
|
'action' => 'unlist',
|
||||||
'message' => 'Success!'
|
'message' => 'Success!',
|
||||||
])
|
])
|
||||||
->accessLevel('admin')
|
->accessLevel('admin')
|
||||||
->save();
|
->save();
|
||||||
|
@ -299,7 +276,7 @@ class InternalApiController extends Controller
|
||||||
->action('admin.status.moderate')
|
->action('admin.status.moderate')
|
||||||
->metadata([
|
->metadata([
|
||||||
'action' => 'spammer',
|
'action' => 'spammer',
|
||||||
'message' => 'Success!'
|
'message' => 'Success!',
|
||||||
])
|
])
|
||||||
->accessLevel('admin')
|
->accessLevel('admin')
|
||||||
->save();
|
->save();
|
||||||
|
@ -307,6 +284,7 @@ class InternalApiController extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
StatusService::del($status->id, true);
|
StatusService::del($status->id, true);
|
||||||
|
|
||||||
return ['msg' => 200];
|
return ['msg' => 200];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -331,6 +309,7 @@ class InternalApiController extends Controller
|
||||||
if ($status) {
|
if ($status) {
|
||||||
BookmarkService::add($pid, $status['id']);
|
BookmarkService::add($pid, $status['id']);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $status;
|
return $status;
|
||||||
})
|
})
|
||||||
->filter(function ($bookmark) {
|
->filter(function ($bookmark) {
|
||||||
|
@ -350,7 +329,7 @@ class InternalApiController extends Controller
|
||||||
'max_id' => 'nullable|integer|min:0|max:'.PHP_INT_MAX,
|
'max_id' => 'nullable|integer|min:0|max:'.PHP_INT_MAX,
|
||||||
'since_id' => 'nullable|integer|min:0|max:'.PHP_INT_MAX,
|
'since_id' => 'nullable|integer|min:0|max:'.PHP_INT_MAX,
|
||||||
'min_id' => 'nullable|integer|min:0|max:'.PHP_INT_MAX,
|
'min_id' => 'nullable|integer|min:0|max:'.PHP_INT_MAX,
|
||||||
'limit' => 'nullable|integer|min:1|max:24'
|
'limit' => 'nullable|integer|min:1|max:24',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$profile = Profile::whereNull('status')->findOrFail($id);
|
$profile = Profile::whereNull('status')->findOrFail($id);
|
||||||
|
@ -369,17 +348,19 @@ class InternalApiController extends Controller
|
||||||
$pid = Auth::user()->profile->id;
|
$pid = Auth::user()->profile->id;
|
||||||
$following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function () use ($pid) {
|
$following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function () use ($pid) {
|
||||||
$following = Follower::whereProfileId($pid)->pluck('following_id');
|
$following = Follower::whereProfileId($pid)->pluck('following_id');
|
||||||
|
|
||||||
return $following->push($pid)->toArray();
|
return $following->push($pid)->toArray();
|
||||||
});
|
});
|
||||||
$visibility = true == in_array($profile->id, $following) ? ['public', 'unlisted', 'private'] : [];
|
$visibility = in_array($profile->id, $following) == true ? ['public', 'unlisted', 'private'] : [];
|
||||||
} else {
|
} else {
|
||||||
if (Auth::check()) {
|
if (Auth::check()) {
|
||||||
$pid = Auth::user()->profile->id;
|
$pid = Auth::user()->profile->id;
|
||||||
$following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function () use ($pid) {
|
$following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function () use ($pid) {
|
||||||
$following = Follower::whereProfileId($pid)->pluck('following_id');
|
$following = Follower::whereProfileId($pid)->pluck('following_id');
|
||||||
|
|
||||||
return $following->push($pid)->toArray();
|
return $following->push($pid)->toArray();
|
||||||
});
|
});
|
||||||
$visibility = true == in_array($profile->id, $following) ? ['public', 'unlisted', 'private'] : ['public', 'unlisted'];
|
$visibility = in_array($profile->id, $following) == true ? ['public', 'unlisted', 'private'] : ['public', 'unlisted'];
|
||||||
} else {
|
} else {
|
||||||
$visibility = ['public', 'unlisted'];
|
$visibility = ['public', 'unlisted'];
|
||||||
}
|
}
|
||||||
|
@ -391,7 +372,6 @@ class InternalApiController extends Controller
|
||||||
'id',
|
'id',
|
||||||
'uri',
|
'uri',
|
||||||
'caption',
|
'caption',
|
||||||
'rendered',
|
|
||||||
'profile_id',
|
'profile_id',
|
||||||
'type',
|
'type',
|
||||||
'in_reply_to_id',
|
'in_reply_to_id',
|
||||||
|
@ -411,7 +391,7 @@ class InternalApiController extends Controller
|
||||||
->limit($limit)
|
->limit($limit)
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
$resource = new Fractal\Resource\Collection($timeline, new StatusTransformer());
|
$resource = new Fractal\Resource\Collection($timeline, new StatusTransformer);
|
||||||
$res = $this->fractal->createData($resource)->toArray();
|
$res = $this->fractal->createData($resource)->toArray();
|
||||||
|
|
||||||
return response()->json($res);
|
return response()->json($res);
|
||||||
|
@ -431,6 +411,7 @@ class InternalApiController extends Controller
|
||||||
{
|
{
|
||||||
$pid = $request->user()->profile_id;
|
$pid = $request->user()->profile_id;
|
||||||
$exists = Redis::sismember('email:manual', $pid);
|
$exists = Redis::sismember('email:manual', $pid);
|
||||||
|
|
||||||
return view('account.email.request_verification', compact('exists'));
|
return view('account.email.request_verification', compact('exists'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -438,6 +419,7 @@ class InternalApiController extends Controller
|
||||||
{
|
{
|
||||||
$pid = $request->user()->profile_id;
|
$pid = $request->user()->profile_id;
|
||||||
Redis::sadd('email:manual', $pid);
|
Redis::sadd('email:manual', $pid);
|
||||||
|
|
||||||
return redirect('/i/verify-email')->with(['status' => 'Successfully sent manual verification request!']);
|
return redirect('/i/verify-email')->with(['status' => 'Successfully sent manual verification request!']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,12 +2,10 @@
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Status;
|
||||||
|
use Auth;
|
||||||
|
use DB;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use App\{
|
|
||||||
Profile,
|
|
||||||
Status,
|
|
||||||
};
|
|
||||||
use Auth, DB, Purify;
|
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
class MicroController extends Controller
|
class MicroController extends Controller
|
||||||
|
@ -23,7 +21,7 @@ class MicroController extends Controller
|
||||||
'type' => [
|
'type' => [
|
||||||
'required',
|
'required',
|
||||||
'string',
|
'string',
|
||||||
Rule::in(['text'])
|
Rule::in(['text']),
|
||||||
],
|
],
|
||||||
'title' => 'nullable|string|max:140',
|
'title' => 'nullable|string|max:140',
|
||||||
'content' => 'required|string|max:500',
|
'content' => 'required|string|max:500',
|
||||||
|
@ -34,9 +32,9 @@ class MicroController extends Controller
|
||||||
'public',
|
'public',
|
||||||
'unlisted',
|
'unlisted',
|
||||||
'private',
|
'private',
|
||||||
'draft'
|
'draft',
|
||||||
])
|
]),
|
||||||
]
|
],
|
||||||
]);
|
]);
|
||||||
$profile = Auth::user()->profile;
|
$profile = Auth::user()->profile;
|
||||||
$title = $request->input('title');
|
$title = $request->input('title');
|
||||||
|
@ -48,7 +46,6 @@ class MicroController extends Controller
|
||||||
$status->type = 'text';
|
$status->type = 'text';
|
||||||
$status->profile_id = $profile->id;
|
$status->profile_id = $profile->id;
|
||||||
$status->caption = strip_tags($content);
|
$status->caption = strip_tags($content);
|
||||||
$status->rendered = Purify::clean($content);
|
|
||||||
$status->is_nsfw = false;
|
$status->is_nsfw = false;
|
||||||
|
|
||||||
// TODO: remove deprecated visibility in favor of scope
|
// TODO: remove deprecated visibility in favor of scope
|
||||||
|
@ -56,12 +53,14 @@ class MicroController extends Controller
|
||||||
$status->scope = $visibility;
|
$status->scope = $visibility;
|
||||||
$status->entities = json_encode(['title' => $title]);
|
$status->entities = json_encode(['title' => $title]);
|
||||||
$status->save();
|
$status->save();
|
||||||
|
|
||||||
return $status;
|
return $status;
|
||||||
});
|
});
|
||||||
|
|
||||||
$fractal = new \League\Fractal\Manager();
|
$fractal = new \League\Fractal\Manager;
|
||||||
$fractal->setSerializer(new \League\Fractal\Serializer\ArraySerializer());
|
$fractal->setSerializer(new \League\Fractal\Serializer\ArraySerializer);
|
||||||
$s = new \League\Fractal\Resource\Item($status, new \App\Transformer\Api\StatusTransformer());
|
$s = new \League\Fractal\Resource\Item($status, new \App\Transformer\Api\StatusTransformer);
|
||||||
|
|
||||||
return $fractal->createData($s)->toArray();
|
return $fractal->createData($s)->toArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
99
app/Http/Controllers/OAuth/OobAuthorizationController.php
Normal file
99
app/Http/Controllers/OAuth/OobAuthorizationController.php
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\OAuth;
|
||||||
|
|
||||||
|
use Laravel\Passport\Http\Controllers\ApproveAuthorizationController;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use League\OAuth2\Server\Exception\OAuthServerException;
|
||||||
|
use Nyholm\Psr7\Response as Psr7Response;
|
||||||
|
|
||||||
|
class OobAuthorizationController extends ApproveAuthorizationController
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Approve the authorization request.
|
||||||
|
*
|
||||||
|
* @param Request $request
|
||||||
|
* @return \Illuminate\Http\Response
|
||||||
|
*/
|
||||||
|
public function approve(Request $request)
|
||||||
|
{
|
||||||
|
$this->assertValidAuthToken($request);
|
||||||
|
|
||||||
|
$authRequest = $this->getAuthRequestFromSession($request);
|
||||||
|
$authRequest->setAuthorizationApproved(true);
|
||||||
|
|
||||||
|
return $this->withErrorHandling(function () use ($authRequest) {
|
||||||
|
$response = $this->server->completeAuthorizationRequest($authRequest, new Psr7Response);
|
||||||
|
|
||||||
|
if ($this->isOutOfBandRequest($authRequest)) {
|
||||||
|
$code = $this->extractAuthorizationCode($response);
|
||||||
|
return response()->json([
|
||||||
|
'code' => $code,
|
||||||
|
'state' => $authRequest->getState()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->convertResponse($response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the request is an out-of-band OAuth request.
|
||||||
|
*
|
||||||
|
* @param \League\OAuth2\Server\RequestTypes\AuthorizationRequest $authRequest
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected function isOutOfBandRequest($authRequest)
|
||||||
|
{
|
||||||
|
return $authRequest->getRedirectUri() === 'urn:ietf:wg:oauth:2.0:oob';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract the authorization code from the PSR-7 response.
|
||||||
|
*
|
||||||
|
* @param \Psr\Http\Message\ResponseInterface $response
|
||||||
|
* @return string
|
||||||
|
* @throws \League\OAuth2\Server\Exception\OAuthServerException
|
||||||
|
*/
|
||||||
|
protected function extractAuthorizationCode($response)
|
||||||
|
{
|
||||||
|
$location = $response->getHeader('Location')[0] ?? '';
|
||||||
|
|
||||||
|
if (empty($location)) {
|
||||||
|
throw OAuthServerException::serverError('Missing authorization code in response');
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_str(parse_url($location, PHP_URL_QUERY), $params);
|
||||||
|
|
||||||
|
if (!isset($params['code'])) {
|
||||||
|
throw OAuthServerException::serverError('Invalid authorization code format');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $params['code'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle OAuth errors for both redirect and OOB flows.
|
||||||
|
*
|
||||||
|
* @param \Closure $callback
|
||||||
|
* @return \Illuminate\Http\Response
|
||||||
|
*/
|
||||||
|
protected function withErrorHandling($callback)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return $callback();
|
||||||
|
} catch (OAuthServerException $e) {
|
||||||
|
if ($this->isOutOfBandRequest($this->getAuthRequestFromSession(request()))) {
|
||||||
|
return response()->json([
|
||||||
|
'error' => $e->getErrorType(),
|
||||||
|
'message' => $e->getMessage(),
|
||||||
|
'hint' => $e->getHint()
|
||||||
|
], $e->getHttpStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->convertResponse(
|
||||||
|
$e->generateHttpResponse(new Psr7Response)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -31,8 +31,8 @@ class PublicApiController extends Controller
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->fractal = new Fractal\Manager();
|
$this->fractal = new Fractal\Manager;
|
||||||
$this->fractal->setSerializer(new ArraySerializer());
|
$this->fractal->setSerializer(new ArraySerializer);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getUserData($user)
|
protected function getUserData($user)
|
||||||
|
@ -74,7 +74,7 @@ class PublicApiController extends Controller
|
||||||
abort_if(! in_array($cached['visibility'], ['public', 'unlisted']), 403);
|
abort_if(! in_array($cached['visibility'], ['public', 'unlisted']), 403);
|
||||||
$res = ['status' => $cached];
|
$res = ['status' => $cached];
|
||||||
} else {
|
} else {
|
||||||
$item = new Fractal\Resource\Item($status, new StatusStatelessTransformer());
|
$item = new Fractal\Resource\Item($status, new StatusStatelessTransformer);
|
||||||
$res = [
|
$res = [
|
||||||
'status' => $this->fractal->createData($item)->toArray(),
|
'status' => $this->fractal->createData($item)->toArray(),
|
||||||
];
|
];
|
||||||
|
@ -141,7 +141,7 @@ class PublicApiController extends Controller
|
||||||
$replies = $status->comments()
|
$replies = $status->comments()
|
||||||
->whereNull('reblog_of_id')
|
->whereNull('reblog_of_id')
|
||||||
->whereIn('scope', $scope)
|
->whereIn('scope', $scope)
|
||||||
->select('id', 'caption', 'local', 'visibility', 'scope', 'is_nsfw', 'rendered', 'profile_id', 'in_reply_to_id', 'type', 'reply_count', 'created_at')
|
->select('id', 'caption', 'local', 'visibility', 'scope', 'is_nsfw', 'profile_id', 'in_reply_to_id', 'type', 'reply_count', 'created_at')
|
||||||
->where('id', '>=', $request->min_id)
|
->where('id', '>=', $request->min_id)
|
||||||
->orderBy('id', 'desc')
|
->orderBy('id', 'desc')
|
||||||
->paginate($limit);
|
->paginate($limit);
|
||||||
|
@ -150,7 +150,7 @@ class PublicApiController extends Controller
|
||||||
$replies = $status->comments()
|
$replies = $status->comments()
|
||||||
->whereNull('reblog_of_id')
|
->whereNull('reblog_of_id')
|
||||||
->whereIn('scope', $scope)
|
->whereIn('scope', $scope)
|
||||||
->select('id', 'caption', 'local', 'visibility', 'scope', 'is_nsfw', 'rendered', 'profile_id', 'in_reply_to_id', 'type', 'reply_count', 'created_at')
|
->select('id', 'caption', 'local', 'visibility', 'scope', 'is_nsfw', 'profile_id', 'in_reply_to_id', 'type', 'reply_count', 'created_at')
|
||||||
->where('id', '<=', $request->max_id)
|
->where('id', '<=', $request->max_id)
|
||||||
->orderBy('id', 'desc')
|
->orderBy('id', 'desc')
|
||||||
->paginate($limit);
|
->paginate($limit);
|
||||||
|
@ -159,12 +159,12 @@ class PublicApiController extends Controller
|
||||||
$replies = Status::whereInReplyToId($status->id)
|
$replies = Status::whereInReplyToId($status->id)
|
||||||
->whereNull('reblog_of_id')
|
->whereNull('reblog_of_id')
|
||||||
->whereIn('scope', $scope)
|
->whereIn('scope', $scope)
|
||||||
->select('id', 'caption', 'local', 'visibility', 'scope', 'is_nsfw', 'rendered', 'profile_id', 'in_reply_to_id', 'type', 'reply_count', 'created_at')
|
->select('id', 'caption', 'local', 'visibility', 'scope', 'is_nsfw', 'profile_id', 'in_reply_to_id', 'type', 'reply_count', 'created_at')
|
||||||
->orderBy('id', 'desc')
|
->orderBy('id', 'desc')
|
||||||
->paginate($limit);
|
->paginate($limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
$resource = new Fractal\Resource\Collection($replies, new StatusStatelessTransformer(), 'data');
|
$resource = new Fractal\Resource\Collection($replies, new StatusStatelessTransformer, 'data');
|
||||||
$resource->setPaginator(new IlluminatePaginatorAdapter($replies));
|
$resource->setPaginator(new IlluminatePaginatorAdapter($replies));
|
||||||
$res = $this->fractal->createData($resource)->toArray();
|
$res = $this->fractal->createData($resource)->toArray();
|
||||||
|
|
||||||
|
@ -271,7 +271,6 @@ class PublicApiController extends Controller
|
||||||
'id',
|
'id',
|
||||||
'uri',
|
'uri',
|
||||||
'caption',
|
'caption',
|
||||||
'rendered',
|
|
||||||
'profile_id',
|
'profile_id',
|
||||||
'type',
|
'type',
|
||||||
'in_reply_to_id',
|
'in_reply_to_id',
|
||||||
|
@ -405,7 +404,6 @@ class PublicApiController extends Controller
|
||||||
'id',
|
'id',
|
||||||
'uri',
|
'uri',
|
||||||
'caption',
|
'caption',
|
||||||
'rendered',
|
|
||||||
'profile_id',
|
'profile_id',
|
||||||
'type',
|
'type',
|
||||||
'in_reply_to_id',
|
'in_reply_to_id',
|
||||||
|
@ -456,7 +454,6 @@ class PublicApiController extends Controller
|
||||||
'id',
|
'id',
|
||||||
'uri',
|
'uri',
|
||||||
'caption',
|
'caption',
|
||||||
'rendered',
|
|
||||||
'profile_id',
|
'profile_id',
|
||||||
'type',
|
'type',
|
||||||
'in_reply_to_id',
|
'in_reply_to_id',
|
||||||
|
|
|
@ -8,6 +8,7 @@ use App\Profile;
|
||||||
use App\Services\WebfingerService;
|
use App\Services\WebfingerService;
|
||||||
use App\Status;
|
use App\Status;
|
||||||
use App\Util\ActivityPub\Helpers;
|
use App\Util\ActivityPub\Helpers;
|
||||||
|
use App\Util\Lexer\Autolink;
|
||||||
use Auth;
|
use Auth;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
@ -320,17 +321,21 @@ class SearchController extends Controller
|
||||||
|
|
||||||
if (Status::whereUri($tag)->whereLocal(false)->exists()) {
|
if (Status::whereUri($tag)->whereLocal(false)->exists()) {
|
||||||
$item = Status::whereUri($tag)->first();
|
$item = Status::whereUri($tag)->first();
|
||||||
|
if (! $item) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
$media = $item->firstMedia();
|
$media = $item->firstMedia();
|
||||||
$url = null;
|
$url = null;
|
||||||
if ($media) {
|
if ($media) {
|
||||||
$url = $media->remote_url;
|
$url = $media->remote_url;
|
||||||
}
|
}
|
||||||
|
$content = $item->caption ? Autolink::create()->autolink($item->caption) : null;
|
||||||
$this->tokens['posts'] = [[
|
$this->tokens['posts'] = [[
|
||||||
'count' => 0,
|
'count' => 0,
|
||||||
'url' => "/i/web/post/_/$item->profile_id/$item->id",
|
'url' => "/i/web/post/_/$item->profile_id/$item->id",
|
||||||
'type' => 'status',
|
'type' => 'status',
|
||||||
'username' => $item->profile->username,
|
'username' => $item->profile->username,
|
||||||
'caption' => $item->rendered ?? $item->caption,
|
'caption' => $content,
|
||||||
'thumb' => $url,
|
'thumb' => $url,
|
||||||
'timestamp' => $item->created_at->diffForHumans(),
|
'timestamp' => $item->created_at->diffForHumans(),
|
||||||
]];
|
]];
|
||||||
|
@ -340,17 +345,21 @@ class SearchController extends Controller
|
||||||
|
|
||||||
if (isset($remote['type']) && $remote['type'] == 'Note') {
|
if (isset($remote['type']) && $remote['type'] == 'Note') {
|
||||||
$item = Helpers::statusFetch($tag);
|
$item = Helpers::statusFetch($tag);
|
||||||
|
if (! $item) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
$media = $item->firstMedia();
|
$media = $item->firstMedia();
|
||||||
$url = null;
|
$url = null;
|
||||||
if ($media) {
|
if ($media) {
|
||||||
$url = $media->remote_url;
|
$url = $media->remote_url;
|
||||||
}
|
}
|
||||||
|
$content = $item->caption ? Autolink::create()->autolink($item->caption) : null;
|
||||||
$this->tokens['posts'] = [[
|
$this->tokens['posts'] = [[
|
||||||
'count' => 0,
|
'count' => 0,
|
||||||
'url' => "/i/web/post/_/$item->profile_id/$item->id",
|
'url' => "/i/web/post/_/$item->profile_id/$item->id",
|
||||||
'type' => 'status',
|
'type' => 'status',
|
||||||
'username' => $item->profile->username,
|
'username' => $item->profile->username,
|
||||||
'caption' => $item->rendered ?? $item->caption,
|
'caption' => $content,
|
||||||
'thumb' => $url,
|
'thumb' => $url,
|
||||||
'timestamp' => $item->created_at->diffForHumans(),
|
'timestamp' => $item->created_at->diffForHumans(),
|
||||||
]];
|
]];
|
||||||
|
|
|
@ -2,25 +2,23 @@
|
||||||
|
|
||||||
namespace App\Http\Controllers\Settings;
|
namespace App\Http\Controllers\Settings;
|
||||||
|
|
||||||
use App\AccountLog;
|
|
||||||
use App\Following;
|
|
||||||
use App\Report;
|
|
||||||
use App\Status;
|
use App\Status;
|
||||||
use App\UserFilter;
|
use App\Transformer\ActivityPub\ProfileTransformer;
|
||||||
use Auth, Cookie, DB, Cache, Purify;
|
|
||||||
use Carbon\Carbon;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use App\Transformer\ActivityPub\{
|
|
||||||
ProfileTransformer,
|
|
||||||
StatusTransformer
|
|
||||||
};
|
|
||||||
use App\Transformer\Api\StatusTransformer as StatusApiTransformer;
|
use App\Transformer\Api\StatusTransformer as StatusApiTransformer;
|
||||||
|
use App\UserFilter;
|
||||||
|
use Auth;
|
||||||
|
use Cache;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
use League\Fractal;
|
use League\Fractal;
|
||||||
use League\Fractal\Serializer\ArraySerializer;
|
use League\Fractal\Serializer\ArraySerializer;
|
||||||
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
|
use Storage;
|
||||||
|
|
||||||
trait ExportSettings
|
trait ExportSettings
|
||||||
{
|
{
|
||||||
|
private const CHUNK_SIZE = 1000;
|
||||||
|
|
||||||
|
private const STORAGE_BASE = 'user_exports';
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->middleware('auth');
|
$this->middleware('auth');
|
||||||
|
@ -33,47 +31,146 @@ trait ExportSettings
|
||||||
|
|
||||||
public function exportAccount()
|
public function exportAccount()
|
||||||
{
|
{
|
||||||
$data = Cache::remember('account:export:profile:actor:'.Auth::user()->profile->id, now()->addMinutes(60), function() {
|
|
||||||
$profile = Auth::user()->profile;
|
$profile = Auth::user()->profile;
|
||||||
$fractal = new Fractal\Manager();
|
$fractal = new Fractal\Manager;
|
||||||
$fractal->setSerializer(new ArraySerializer());
|
$fractal->setSerializer(new ArraySerializer);
|
||||||
$resource = new Fractal\Resource\Item($profile, new ProfileTransformer());
|
$resource = new Fractal\Resource\Item($profile, new ProfileTransformer);
|
||||||
return $fractal->createData($resource)->toArray();
|
|
||||||
});
|
$data = $fractal->createData($resource)->toArray();
|
||||||
|
|
||||||
return response()->streamDownload(function () use ($data) {
|
return response()->streamDownload(function () use ($data) {
|
||||||
echo json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
echo json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||||
}, 'account.json', [
|
}, 'account.json', [
|
||||||
'Content-Type' => 'application/json'
|
'Content-Type' => 'application/json',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function exportFollowing()
|
public function exportFollowing()
|
||||||
{
|
{
|
||||||
$data = Cache::remember('account:export:profile:following:'.Auth::user()->profile->id, now()->addMinutes(60), function() {
|
$profile = Auth::user()->profile;
|
||||||
return Auth::user()->profile->following()->get()->map(function($i) {
|
$userId = Auth::id();
|
||||||
return $i->url();
|
|
||||||
|
$userExportPath = 'user_exports/'.$userId;
|
||||||
|
$filename = 'pixelfed-following.json';
|
||||||
|
$tempPath = $userExportPath.'/'.$filename;
|
||||||
|
|
||||||
|
if (! Storage::exists($userExportPath)) {
|
||||||
|
Storage::makeDirectory($userExportPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Storage::put($tempPath, '[');
|
||||||
|
|
||||||
|
$profile->following()
|
||||||
|
->chunk(1000, function ($following) use ($tempPath) {
|
||||||
|
$urls = $following->map(function ($follow) {
|
||||||
|
return $follow->url();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$json = json_encode($urls,
|
||||||
|
JSON_PRETTY_PRINT |
|
||||||
|
JSON_UNESCAPED_SLASHES |
|
||||||
|
JSON_UNESCAPED_UNICODE
|
||||||
|
);
|
||||||
|
|
||||||
|
$json = trim($json, '[]');
|
||||||
|
if (Storage::size($tempPath) > 1) {
|
||||||
|
$json = ','.$json;
|
||||||
|
}
|
||||||
|
|
||||||
|
Storage::append($tempPath, $json);
|
||||||
});
|
});
|
||||||
return response()->streamDownload(function () use($data) {
|
|
||||||
echo $data;
|
Storage::append($tempPath, ']');
|
||||||
}, 'following.json', [
|
|
||||||
'Content-Type' => 'application/json'
|
return response()->stream(
|
||||||
]);
|
function () use ($tempPath) {
|
||||||
|
$handle = fopen(Storage::path($tempPath), 'rb');
|
||||||
|
while (! feof($handle)) {
|
||||||
|
echo fread($handle, 8192);
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
fclose($handle);
|
||||||
|
|
||||||
|
Storage::delete($tempPath);
|
||||||
|
},
|
||||||
|
200,
|
||||||
|
[
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'Content-Disposition' => 'attachment; filename="pixelfed-following.json"',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
if (Storage::exists($tempPath)) {
|
||||||
|
Storage::delete($tempPath);
|
||||||
|
}
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function exportFollowers()
|
public function exportFollowers()
|
||||||
{
|
{
|
||||||
$data = Cache::remember('account:export:profile:followers:'.Auth::user()->profile->id, now()->addMinutes(60), function() {
|
$profile = Auth::user()->profile;
|
||||||
return Auth::user()->profile->followers()->get()->map(function($i) {
|
$userId = Auth::id();
|
||||||
return $i->url();
|
|
||||||
|
$userExportPath = 'user_exports/'.$userId;
|
||||||
|
$filename = 'pixelfed-followers.json';
|
||||||
|
$tempPath = $userExportPath.'/'.$filename;
|
||||||
|
|
||||||
|
if (! Storage::exists($userExportPath)) {
|
||||||
|
Storage::makeDirectory($userExportPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Storage::put($tempPath, '[');
|
||||||
|
|
||||||
|
$profile->followers()
|
||||||
|
->chunk(1000, function ($followers) use ($tempPath) {
|
||||||
|
$urls = $followers->map(function ($follower) {
|
||||||
|
return $follower->url();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$json = json_encode($urls,
|
||||||
|
JSON_PRETTY_PRINT |
|
||||||
|
JSON_UNESCAPED_SLASHES |
|
||||||
|
JSON_UNESCAPED_UNICODE
|
||||||
|
);
|
||||||
|
|
||||||
|
$json = trim($json, '[]');
|
||||||
|
if (Storage::size($tempPath) > 1) {
|
||||||
|
$json = ','.$json;
|
||||||
|
}
|
||||||
|
|
||||||
|
Storage::append($tempPath, $json);
|
||||||
});
|
});
|
||||||
return response()->streamDownload(function () use($data) {
|
|
||||||
echo $data;
|
Storage::append($tempPath, ']');
|
||||||
}, 'followers.json', [
|
|
||||||
'Content-Type' => 'application/json'
|
return response()->stream(
|
||||||
]);
|
function () use ($tempPath) {
|
||||||
|
$handle = fopen(Storage::path($tempPath), 'rb');
|
||||||
|
while (! feof($handle)) {
|
||||||
|
echo fread($handle, 8192);
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
fclose($handle);
|
||||||
|
|
||||||
|
Storage::delete($tempPath);
|
||||||
|
},
|
||||||
|
200,
|
||||||
|
[
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'Content-Disposition' => 'attachment; filename="pixelfed-followers.json"',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
if (Storage::exists($tempPath)) {
|
||||||
|
Storage::delete($tempPath);
|
||||||
|
}
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function exportMuteBlockList()
|
public function exportMuteBlockList()
|
||||||
|
@ -88,57 +185,77 @@ trait ExportSettings
|
||||||
$data = Cache::remember('account:export:profile:muteblocklist:'.Auth::user()->profile->id, now()->addMinutes(60), function () use ($profile) {
|
$data = Cache::remember('account:export:profile:muteblocklist:'.Auth::user()->profile->id, now()->addMinutes(60), function () use ($profile) {
|
||||||
return json_encode([
|
return json_encode([
|
||||||
'muted' => $profile->mutedProfileUrls(),
|
'muted' => $profile->mutedProfileUrls(),
|
||||||
'blocked' => $profile->blockedProfileUrls()
|
'blocked' => $profile->blockedProfileUrls(),
|
||||||
], JSON_PRETTY_PRINT);
|
], JSON_PRETTY_PRINT);
|
||||||
});
|
});
|
||||||
|
|
||||||
return response()->streamDownload(function () use ($data) {
|
return response()->streamDownload(function () use ($data) {
|
||||||
echo $data;
|
echo $data;
|
||||||
}, 'muted-and-blocked-accounts.json', [
|
}, 'muted-and-blocked-accounts.json', [
|
||||||
'Content-Type' => 'application/json'
|
'Content-Type' => 'application/json',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function exportStatuses(Request $request)
|
public function exportStatuses(Request $request)
|
||||||
{
|
{
|
||||||
$this->validate($request, [
|
|
||||||
'type' => 'required|string|in:ap,api'
|
|
||||||
]);
|
|
||||||
$limit = 500;
|
|
||||||
|
|
||||||
$profile = Auth::user()->profile;
|
$profile = Auth::user()->profile;
|
||||||
$type = 'ap';
|
$userId = Auth::id();
|
||||||
|
$userExportPath = self::STORAGE_BASE.'/'.$userId;
|
||||||
|
$filename = 'pixelfed-statuses.json';
|
||||||
|
$tempPath = $userExportPath.'/'.$filename;
|
||||||
|
|
||||||
$count = Status::select('id')->whereProfileId($profile->id)->count();
|
if (! Storage::exists($userExportPath)) {
|
||||||
if($count > $limit) {
|
Storage::makeDirectory($userExportPath);
|
||||||
// fire background job
|
|
||||||
return redirect('/settings/data-export')->with(['status' => 'You have more than '.$limit.' statuses, we do not support full account export yet.']);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$filename = 'outbox.json';
|
Storage::put($tempPath, '[');
|
||||||
if($type == 'ap') {
|
$fractal = new Fractal\Manager;
|
||||||
$data = Cache::remember('account:export:profile:statuses:ap:'.Auth::user()->profile->id, now()->addHours(1), function() {
|
$fractal->setSerializer(new ArraySerializer);
|
||||||
$profile = Auth::user()->profile->statuses;
|
|
||||||
$fractal = new Fractal\Manager();
|
try {
|
||||||
$fractal->setSerializer(new ArraySerializer());
|
Status::whereProfileId($profile->id)
|
||||||
$resource = new Fractal\Resource\Collection($profile, new StatusTransformer());
|
->chunk(self::CHUNK_SIZE, function ($statuses) use ($fractal, $tempPath) {
|
||||||
return $fractal->createData($resource)->toArray();
|
$resource = new Fractal\Resource\Collection($statuses, new StatusApiTransformer);
|
||||||
|
$data = $fractal->createData($resource)->toArray();
|
||||||
|
|
||||||
|
$json = json_encode($data,
|
||||||
|
JSON_PRETTY_PRINT |
|
||||||
|
JSON_UNESCAPED_SLASHES |
|
||||||
|
JSON_UNESCAPED_UNICODE
|
||||||
|
);
|
||||||
|
|
||||||
|
$json = trim($json, '[]');
|
||||||
|
if (Storage::size($tempPath) > 1) {
|
||||||
|
$json = ','.$json;
|
||||||
|
}
|
||||||
|
|
||||||
|
Storage::append($tempPath, $json);
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
$filename = 'api-statuses.json';
|
|
||||||
$data = Cache::remember('account:export:profile:statuses:api:'.Auth::user()->profile->id, now()->addHours(1), function() {
|
|
||||||
$profile = Auth::user()->profile->statuses;
|
|
||||||
$fractal = new Fractal\Manager();
|
|
||||||
$fractal->setSerializer(new ArraySerializer());
|
|
||||||
$resource = new Fractal\Resource\Collection($profile, new StatusApiTransformer());
|
|
||||||
return $fractal->createData($resource)->toArray();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return response()->streamDownload(function () use ($data, $filename) {
|
Storage::append($tempPath, ']');
|
||||||
echo json_encode($data, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE);
|
|
||||||
}, $filename, [
|
|
||||||
'Content-Type' => 'application/json'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
return response()->stream(
|
||||||
|
function () use ($tempPath) {
|
||||||
|
$handle = fopen(Storage::path($tempPath), 'rb');
|
||||||
|
while (! feof($handle)) {
|
||||||
|
echo fread($handle, 8192);
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
fclose($handle);
|
||||||
|
Storage::delete($tempPath);
|
||||||
|
},
|
||||||
|
200,
|
||||||
|
[
|
||||||
|
'Content-Type' => 'application/json',
|
||||||
|
'Content-Disposition' => 'attachment; filename="pixelfed-statuses.json"',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
if (Storage::exists($tempPath)) {
|
||||||
|
Storage::delete($tempPath);
|
||||||
|
}
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -309,7 +309,7 @@ class StatusController extends Controller
|
||||||
abort_if(! $statusAccount || isset($statusAccount['moved'], $statusAccount['moved']['id']), 422, 'Account moved');
|
abort_if(! $statusAccount || isset($statusAccount['moved'], $statusAccount['moved']['id']), 422, 'Account moved');
|
||||||
|
|
||||||
$count = $status->reblogs_count;
|
$count = $status->reblogs_count;
|
||||||
|
$defaultCaption = config_cache('database.default') === 'mysql' ? null : "";
|
||||||
$exists = Status::whereProfileId(Auth::user()->profile->id)
|
$exists = Status::whereProfileId(Auth::user()->profile->id)
|
||||||
->whereReblogOfId($status->id)
|
->whereReblogOfId($status->id)
|
||||||
->exists();
|
->exists();
|
||||||
|
@ -324,6 +324,8 @@ class StatusController extends Controller
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$share = new Status;
|
$share = new Status;
|
||||||
|
$share->caption = $defaultCaption;
|
||||||
|
$share->rendered = $defaultCaption;
|
||||||
$share->profile_id = $profile->id;
|
$share->profile_id = $profile->id;
|
||||||
$share->reblog_of_id = $status->id;
|
$share->reblog_of_id = $status->id;
|
||||||
$share->in_reply_to_profile_id = $status->profile_id;
|
$share->in_reply_to_profile_id = $status->profile_id;
|
||||||
|
|
|
@ -281,7 +281,7 @@ class StoryApiV1Controller extends Controller
|
||||||
$photo = $request->file('file');
|
$photo = $request->file('file');
|
||||||
$path = $this->storeMedia($photo, $user);
|
$path = $this->storeMedia($photo, $user);
|
||||||
|
|
||||||
$story = new Story();
|
$story = new Story;
|
||||||
$story->duration = $request->input('duration', 3);
|
$story->duration = $request->input('duration', 3);
|
||||||
$story->profile_id = $user->profile_id;
|
$story->profile_id = $user->profile_id;
|
||||||
$story->type = Str::endsWith($photo->getMimeType(), 'mp4') ? 'video' : 'photo';
|
$story->type = Str::endsWith($photo->getMimeType(), 'mp4') ? 'video' : 'photo';
|
||||||
|
@ -418,7 +418,6 @@ class StoryApiV1Controller extends Controller
|
||||||
$status->type = 'story:reply';
|
$status->type = 'story:reply';
|
||||||
$status->profile_id = $pid;
|
$status->profile_id = $pid;
|
||||||
$status->caption = $text;
|
$status->caption = $text;
|
||||||
$status->rendered = $text;
|
|
||||||
$status->scope = 'direct';
|
$status->scope = 'direct';
|
||||||
$status->visibility = 'direct';
|
$status->visibility = 'direct';
|
||||||
$status->in_reply_to_profile_id = $story->profile_id;
|
$status->in_reply_to_profile_id = $story->profile_id;
|
||||||
|
|
|
@ -54,7 +54,7 @@ class StoryComposeController extends Controller
|
||||||
$photo = $request->file('file');
|
$photo = $request->file('file');
|
||||||
$path = $this->storePhoto($photo, $user);
|
$path = $this->storePhoto($photo, $user);
|
||||||
|
|
||||||
$story = new Story();
|
$story = new Story;
|
||||||
$story->duration = 3;
|
$story->duration = 3;
|
||||||
$story->profile_id = $user->profile_id;
|
$story->profile_id = $user->profile_id;
|
||||||
$story->type = Str::endsWith($photo->getMimeType(), 'mp4') ? 'video' : 'photo';
|
$story->type = Str::endsWith($photo->getMimeType(), 'mp4') ? 'video' : 'photo';
|
||||||
|
@ -403,7 +403,6 @@ class StoryComposeController extends Controller
|
||||||
$status->profile_id = $pid;
|
$status->profile_id = $pid;
|
||||||
$status->type = 'story:reaction';
|
$status->type = 'story:reaction';
|
||||||
$status->caption = $text;
|
$status->caption = $text;
|
||||||
$status->rendered = $text;
|
|
||||||
$status->scope = 'direct';
|
$status->scope = 'direct';
|
||||||
$status->visibility = 'direct';
|
$status->visibility = 'direct';
|
||||||
$status->in_reply_to_profile_id = $story->profile_id;
|
$status->in_reply_to_profile_id = $story->profile_id;
|
||||||
|
@ -477,7 +476,6 @@ class StoryComposeController extends Controller
|
||||||
$status->type = 'story:reply';
|
$status->type = 'story:reply';
|
||||||
$status->profile_id = $pid;
|
$status->profile_id = $pid;
|
||||||
$status->caption = $text;
|
$status->caption = $text;
|
||||||
$status->rendered = $text;
|
|
||||||
$status->scope = 'direct';
|
$status->scope = 'direct';
|
||||||
$status->visibility = 'direct';
|
$status->visibility = 'direct';
|
||||||
$status->in_reply_to_profile_id = $story->profile_id;
|
$status->in_reply_to_profile_id = $story->profile_id;
|
||||||
|
|
|
@ -12,6 +12,7 @@ class VerifyCsrfToken extends Middleware
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $except = [
|
protected $except = [
|
||||||
'/api/v1/*'
|
'/api/v1/*',
|
||||||
|
'oauth/token'
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,18 +2,17 @@
|
||||||
|
|
||||||
namespace App\Http\Resources;
|
namespace App\Http\Resources;
|
||||||
|
|
||||||
use Illuminate\Http\Resources\Json\JsonResource;
|
use App\Models\CustomEmoji;
|
||||||
use Cache;
|
|
||||||
use App\Services\AccountService;
|
use App\Services\AccountService;
|
||||||
use App\Services\HashidService;
|
use App\Services\HashidService;
|
||||||
use App\Services\LikeService;
|
use App\Services\LikeService;
|
||||||
use App\Services\MediaService;
|
use App\Services\MediaService;
|
||||||
use App\Services\MediaTagService;
|
use App\Services\MediaTagService;
|
||||||
use App\Services\StatusHashtagService;
|
|
||||||
use App\Services\StatusLabelService;
|
|
||||||
use App\Services\StatusMentionService;
|
|
||||||
use App\Services\PollService;
|
use App\Services\PollService;
|
||||||
use App\Models\CustomEmoji;
|
use App\Services\StatusHashtagService;
|
||||||
|
use App\Services\StatusMentionService;
|
||||||
|
use App\Util\Lexer\Autolink;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
class StatusStateless extends JsonResource
|
class StatusStateless extends JsonResource
|
||||||
{
|
{
|
||||||
|
@ -28,6 +27,7 @@ class StatusStateless extends JsonResource
|
||||||
$status = $this;
|
$status = $this;
|
||||||
$taggedPeople = MediaTagService::get($status->id);
|
$taggedPeople = MediaTagService::get($status->id);
|
||||||
$poll = $status->type === 'poll' ? PollService::get($status->id) : null;
|
$poll = $status->type === 'poll' ? PollService::get($status->id) : null;
|
||||||
|
$autoLink = $status->caption ? Autolink::create()->autolink($status->caption) : null;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'_v' => 1,
|
'_v' => 1,
|
||||||
|
@ -39,7 +39,7 @@ class StatusStateless extends JsonResource
|
||||||
'in_reply_to_id' => $status->in_reply_to_id ? (string) $status->in_reply_to_id : null,
|
'in_reply_to_id' => $status->in_reply_to_id ? (string) $status->in_reply_to_id : null,
|
||||||
'in_reply_to_account_id' => $status->in_reply_to_profile_id ? (string) $status->in_reply_to_profile_id : null,
|
'in_reply_to_account_id' => $status->in_reply_to_profile_id ? (string) $status->in_reply_to_profile_id : null,
|
||||||
'reblog' => null,
|
'reblog' => null,
|
||||||
'content' => $status->rendered ?? $status->caption,
|
'content' => $autoLink,
|
||||||
'content_text' => $status->caption,
|
'content_text' => $status->caption,
|
||||||
'created_at' => str_replace('+00:00', 'Z', $status->created_at->format(DATE_RFC3339_EXTENDED)),
|
'created_at' => str_replace('+00:00', 'Z', $status->created_at->format(DATE_RFC3339_EXTENDED)),
|
||||||
'emojis' => CustomEmoji::scan($status->caption),
|
'emojis' => CustomEmoji::scan($status->caption),
|
||||||
|
@ -53,7 +53,7 @@ class StatusStateless extends JsonResource
|
||||||
'visibility' => $status->scope ?? $status->visibility,
|
'visibility' => $status->scope ?? $status->visibility,
|
||||||
'application' => [
|
'application' => [
|
||||||
'name' => 'web',
|
'name' => 'web',
|
||||||
'website' => null
|
'website' => null,
|
||||||
],
|
],
|
||||||
'language' => null,
|
'language' => null,
|
||||||
'mentions' => StatusMentionService::get($status->id),
|
'mentions' => StatusMentionService::get($status->id),
|
||||||
|
@ -70,7 +70,7 @@ class StatusStateless extends JsonResource
|
||||||
'media_attachments' => MediaService::get($status->id),
|
'media_attachments' => MediaService::get($status->id),
|
||||||
'account' => AccountService::get($status->profile_id, true),
|
'account' => AccountService::get($status->profile_id, true),
|
||||||
'tags' => StatusHashtagService::statusTags($status->id),
|
'tags' => StatusHashtagService::statusTags($status->id),
|
||||||
'poll' => $poll
|
'poll' => $poll,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,35 +2,32 @@
|
||||||
|
|
||||||
namespace App\Jobs\GroupPipeline;
|
namespace App\Jobs\GroupPipeline;
|
||||||
|
|
||||||
use App\Notification;
|
|
||||||
use App\Hashtag;
|
use App\Hashtag;
|
||||||
use App\Mention;
|
use App\Mention;
|
||||||
use App\Profile;
|
|
||||||
use App\Status;
|
|
||||||
use App\StatusHashtag;
|
|
||||||
use App\Models\GroupPostHashtag;
|
|
||||||
use App\Models\GroupPost;
|
use App\Models\GroupPost;
|
||||||
use Cache;
|
use App\Models\GroupPostHashtag;
|
||||||
|
use App\Profile;
|
||||||
|
use App\Services\StatusService;
|
||||||
|
use App\Status;
|
||||||
|
use App\Util\Lexer\Autolink;
|
||||||
|
use App\Util\Lexer\Extractor;
|
||||||
use DB;
|
use DB;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Support\Facades\Redis;
|
|
||||||
use App\Services\MediaStorageService;
|
|
||||||
use App\Services\NotificationService;
|
|
||||||
use App\Services\StatusService;
|
|
||||||
use App\Util\Lexer\Autolink;
|
|
||||||
use App\Util\Lexer\Extractor;
|
|
||||||
|
|
||||||
class NewStatusPipeline implements ShouldQueue
|
class NewStatusPipeline implements ShouldQueue
|
||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
protected $status;
|
protected $status;
|
||||||
|
|
||||||
protected $gp;
|
protected $gp;
|
||||||
|
|
||||||
protected $tags;
|
protected $tags;
|
||||||
|
|
||||||
protected $mentions;
|
protected $mentions;
|
||||||
|
|
||||||
public function __construct(Status $status, GroupPost $gp)
|
public function __construct(Status $status, GroupPost $gp)
|
||||||
|
@ -50,10 +47,6 @@ class NewStatusPipeline implements ShouldQueue
|
||||||
->autolink($status->caption);
|
->autolink($status->caption);
|
||||||
|
|
||||||
$entities = Extractor::create()->extract($status->caption);
|
$entities = Extractor::create()->extract($status->caption);
|
||||||
|
|
||||||
$autolink = str_replace('/discover/tags/', '/groups/' . $status->group_id . '/topics/', $autolink);
|
|
||||||
|
|
||||||
$status->rendered = nl2br($autolink);
|
|
||||||
$status->entities = null;
|
$status->entities = null;
|
||||||
$status->save();
|
$status->save();
|
||||||
|
|
||||||
|
@ -117,7 +110,7 @@ class NewStatusPipeline implements ShouldQueue
|
||||||
}
|
}
|
||||||
|
|
||||||
DB::transaction(function () use ($status, $mentioned) {
|
DB::transaction(function () use ($status, $mentioned) {
|
||||||
$m = new Mention();
|
$m = new Mention;
|
||||||
$m->status_id = $status->id;
|
$m->status_id = $status->id;
|
||||||
$m->profile_id = $mentioned->id;
|
$m->profile_id = $mentioned->id;
|
||||||
$m->save();
|
$m->save();
|
||||||
|
|
|
@ -91,11 +91,6 @@ class StatusEntityLexer implements ShouldQueue
|
||||||
public function storeEntities()
|
public function storeEntities()
|
||||||
{
|
{
|
||||||
$this->storeHashtags();
|
$this->storeHashtags();
|
||||||
DB::transaction(function () {
|
|
||||||
$status = $this->status;
|
|
||||||
$status->rendered = nl2br($this->autolink);
|
|
||||||
$status->save();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function storeHashtags()
|
public function storeHashtags()
|
||||||
|
@ -146,7 +141,7 @@ class StatusEntityLexer implements ShouldQueue
|
||||||
}
|
}
|
||||||
|
|
||||||
DB::transaction(function () use ($status, $mentioned) {
|
DB::transaction(function () use ($status, $mentioned) {
|
||||||
$m = new Mention();
|
$m = new Mention;
|
||||||
$m->status_id = $status->id;
|
$m->status_id = $status->id;
|
||||||
$m->profile_id = $mentioned->id;
|
$m->profile_id = $mentioned->id;
|
||||||
$m->save();
|
$m->save();
|
||||||
|
|
|
@ -120,8 +120,7 @@ class StatusRemoteUpdatePipeline implements ShouldQueue
|
||||||
protected function updateImmediateAttributes($status, $activity)
|
protected function updateImmediateAttributes($status, $activity)
|
||||||
{
|
{
|
||||||
if (isset($activity['content'])) {
|
if (isset($activity['content'])) {
|
||||||
$status->caption = strip_tags($activity['content']);
|
$status->caption = strip_tags(Purify::clean($activity['content']));
|
||||||
$status->rendered = Purify::clean($activity['content']);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($activity['sensitive'])) {
|
if (isset($activity['sensitive'])) {
|
||||||
|
|
|
@ -2,27 +2,28 @@
|
||||||
|
|
||||||
namespace App\Jobs\StatusPipeline;
|
namespace App\Jobs\StatusPipeline;
|
||||||
|
|
||||||
|
use App\Hashtag;
|
||||||
|
use App\Jobs\MentionPipeline\MentionPipeline;
|
||||||
|
use App\Mention;
|
||||||
|
use App\Services\AccountService;
|
||||||
|
use App\Services\CustomEmojiService;
|
||||||
|
use App\Services\StatusService;
|
||||||
|
use App\Services\TrendingHashtagService;
|
||||||
|
use App\StatusHashtag;
|
||||||
|
use App\Util\ActivityPub\Helpers;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use App\Services\AccountService;
|
use Illuminate\Support\Facades\DB;
|
||||||
use App\Services\CustomEmojiService;
|
|
||||||
use App\Services\StatusService;
|
|
||||||
use App\Jobs\MentionPipeline\MentionPipeline;
|
|
||||||
use App\Mention;
|
|
||||||
use App\Hashtag;
|
|
||||||
use App\StatusHashtag;
|
|
||||||
use App\Services\TrendingHashtagService;
|
|
||||||
use App\Util\ActivityPub\Helpers;
|
|
||||||
|
|
||||||
class StatusTagsPipeline implements ShouldQueue
|
class StatusTagsPipeline implements ShouldQueue
|
||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
protected $activity;
|
protected $activity;
|
||||||
|
|
||||||
protected $status;
|
protected $status;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -77,28 +78,62 @@ class StatusTagsPipeline implements ShouldQueue
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config('database.default') === 'pgsql') {
|
if (config('database.default') === 'pgsql') {
|
||||||
$hashtag = Hashtag::where('name', 'ilike', $name)
|
$hashtag = DB::transaction(function () use ($name) {
|
||||||
->orWhere('slug', 'ilike', str_slug($name, '-', false))
|
$baseSlug = str_slug($name, '-', false);
|
||||||
|
$slug = $baseSlug;
|
||||||
|
$counter = 1;
|
||||||
|
|
||||||
|
$existing = Hashtag::where('name', $name)
|
||||||
|
->lockForUpdate()
|
||||||
->first();
|
->first();
|
||||||
|
|
||||||
if(!$hashtag) {
|
if ($existing) {
|
||||||
$hashtag = Hashtag::updateOrCreate([
|
if ($existing->slug !== $slug) {
|
||||||
'slug' => str_slug($name, '-', false),
|
while (Hashtag::where('slug', $slug)
|
||||||
'name' => $name
|
->where('name', '!=', $name)
|
||||||
]);
|
->exists()) {
|
||||||
|
$slug = $baseSlug.'-'.$counter++;
|
||||||
}
|
}
|
||||||
} else {
|
$existing->slug = $slug;
|
||||||
$hashtag = Hashtag::updateOrCreate([
|
$existing->save();
|
||||||
'slug' => str_slug($name, '-', false),
|
}
|
||||||
'name' => $name
|
|
||||||
|
return $existing;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (Hashtag::where('slug', $slug)->exists()) {
|
||||||
|
$slug = $baseSlug.'-'.$counter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Hashtag::create([
|
||||||
|
'name' => $name,
|
||||||
|
'slug' => $slug,
|
||||||
]);
|
]);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
$hashtag = DB::transaction(function () use ($name) {
|
||||||
|
$baseSlug = str_slug($name, '-', false);
|
||||||
|
$slug = $baseSlug;
|
||||||
|
$counter = 1;
|
||||||
|
|
||||||
|
while (Hashtag::where('slug', $slug)
|
||||||
|
->where('name', '!=', $name)
|
||||||
|
->exists()) {
|
||||||
|
$slug = $baseSlug.'-'.$counter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Hashtag::updateOrCreate(
|
||||||
|
['name' => $name],
|
||||||
|
['slug' => $slug]
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
StatusHashtag::firstOrCreate([
|
StatusHashtag::firstOrCreate([
|
||||||
'status_id' => $status->id,
|
'status_id' => $status->id,
|
||||||
'hashtag_id' => $hashtag->id,
|
'hashtag_id' => $hashtag->id,
|
||||||
'profile_id' => $status->profile_id,
|
'profile_id' => $status->profile_id,
|
||||||
'status_visibility' => $status->scope
|
'status_visibility' => $status->scope,
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ class Media extends Model
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
'srcset' => 'array',
|
'srcset' => 'array',
|
||||||
'deleted_at' => 'datetime',
|
'deleted_at' => 'datetime',
|
||||||
|
'skip_optimize' => 'boolean'
|
||||||
];
|
];
|
||||||
|
|
||||||
public function status()
|
public function status()
|
||||||
|
|
|
@ -2,39 +2,39 @@
|
||||||
|
|
||||||
namespace App\Providers;
|
namespace App\Providers;
|
||||||
|
|
||||||
use App\Observers\{
|
use App\Avatar;
|
||||||
AvatarObserver,
|
use App\Follower;
|
||||||
FollowerObserver,
|
use App\HashtagFollow;
|
||||||
HashtagFollowObserver,
|
use App\Like;
|
||||||
LikeObserver,
|
use App\ModLog;
|
||||||
NotificationObserver,
|
use App\Notification;
|
||||||
ModLogObserver,
|
use App\Observers\AvatarObserver;
|
||||||
ProfileObserver,
|
use App\Observers\FollowerObserver;
|
||||||
StatusHashtagObserver,
|
use App\Observers\HashtagFollowObserver;
|
||||||
StatusObserver,
|
use App\Observers\LikeObserver;
|
||||||
UserObserver,
|
use App\Observers\ModLogObserver;
|
||||||
UserFilterObserver,
|
use App\Observers\NotificationObserver;
|
||||||
};
|
use App\Observers\ProfileObserver;
|
||||||
use App\{
|
use App\Observers\StatusHashtagObserver;
|
||||||
Avatar,
|
use App\Observers\StatusObserver;
|
||||||
Follower,
|
use App\Observers\UserFilterObserver;
|
||||||
HashtagFollow,
|
use App\Observers\UserObserver;
|
||||||
Like,
|
use App\Profile;
|
||||||
Notification,
|
use App\Services\AccountService;
|
||||||
ModLog,
|
use App\Status;
|
||||||
Profile,
|
use App\StatusHashtag;
|
||||||
StatusHashtag,
|
use App\User;
|
||||||
Status,
|
use App\UserFilter;
|
||||||
User,
|
use Auth;
|
||||||
UserFilter
|
use Horizon;
|
||||||
};
|
|
||||||
use Auth, Horizon, URL;
|
|
||||||
use Illuminate\Support\Facades\Blade;
|
|
||||||
use Illuminate\Support\Facades\Schema;
|
|
||||||
use Illuminate\Support\ServiceProvider;
|
|
||||||
use Illuminate\Pagination\Paginator;
|
|
||||||
use Illuminate\Support\Facades\Validator;
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Pagination\Paginator;
|
||||||
|
use Illuminate\Support\Facades\Gate;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
use Laravel\Pulse\Facades\Pulse;
|
||||||
|
use URL;
|
||||||
|
|
||||||
class AppServiceProvider extends ServiceProvider
|
class AppServiceProvider extends ServiceProvider
|
||||||
{
|
{
|
||||||
|
@ -67,6 +67,24 @@ class AppServiceProvider extends ServiceProvider
|
||||||
});
|
});
|
||||||
Validator::includeUnvalidatedArrayKeys();
|
Validator::includeUnvalidatedArrayKeys();
|
||||||
|
|
||||||
|
Gate::define('viewPulse', function (User $user) {
|
||||||
|
return $user->is_admin === 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
Pulse::user(function ($user) {
|
||||||
|
$acct = AccountService::get($user->profile_id, true);
|
||||||
|
|
||||||
|
return $acct ? [
|
||||||
|
'name' => $acct['username'],
|
||||||
|
'extra' => $user->email,
|
||||||
|
'avatar' => $acct['avatar'],
|
||||||
|
] : [
|
||||||
|
'name' => $user->username,
|
||||||
|
'extra' => 'DELETED',
|
||||||
|
'avatar' => '/storage/avatars/default.jpg',
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
// Model::preventLazyLoading(true);
|
// Model::preventLazyLoading(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ class AuthServiceProvider extends ServiceProvider
|
||||||
public function boot()
|
public function boot()
|
||||||
{
|
{
|
||||||
if(config('pixelfed.oauth_enabled') == true) {
|
if(config('pixelfed.oauth_enabled') == true) {
|
||||||
|
Passport::ignoreRoutes();
|
||||||
Passport::tokensExpireIn(now()->addDays(config('instance.oauth.token_expiration', 356)));
|
Passport::tokensExpireIn(now()->addDays(config('instance.oauth.token_expiration', 356)));
|
||||||
Passport::refreshTokensExpireIn(now()->addDays(config('instance.oauth.refresh_expiration', 400)));
|
Passport::refreshTokensExpireIn(now()->addDays(config('instance.oauth.refresh_expiration', 400)));
|
||||||
Passport::enableImplicitGrant();
|
Passport::enableImplicitGrant();
|
||||||
|
|
|
@ -2,53 +2,25 @@
|
||||||
|
|
||||||
namespace App\Services;
|
namespace App\Services;
|
||||||
|
|
||||||
use Cache;
|
|
||||||
use App\Profile;
|
use App\Profile;
|
||||||
use Illuminate\Support\Str;
|
use Cache;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Purify;
|
||||||
use App\Util\Webfinger\WebfingerUrl;
|
|
||||||
|
|
||||||
class AutolinkService
|
class AutolinkService
|
||||||
{
|
{
|
||||||
const CACHE_KEY = 'pf:services:autolink:';
|
const CACHE_KEY = 'pf:services:autolink:mue:';
|
||||||
|
|
||||||
public static function mentionedUsernameExists($username)
|
public static function mentionedUsernameExists($username)
|
||||||
{
|
{
|
||||||
$key = 'pf:services:autolink:userexists:' . hash('sha256', $username);
|
if (str_starts_with($username, '@')) {
|
||||||
|
if (substr_count($username, '@') === 1) {
|
||||||
|
$username = substr($username, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$name = Purify::clean(strtolower($username));
|
||||||
|
|
||||||
return Cache::remember($key, 3600, function() use($username) {
|
return Cache::remember(self::CACHE_KEY.base64_encode($name), 7200, function () use ($name) {
|
||||||
$remote = Str::of($username)->contains('@');
|
return Profile::where('username', $name)->exists();
|
||||||
$profile = Profile::whereUsername($username)->first();
|
|
||||||
if($profile) {
|
|
||||||
if($profile->domain != null) {
|
|
||||||
$instance = InstanceService::getByDomain($profile->domain);
|
|
||||||
if($instance && $instance->banned == true) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
if($remote) {
|
|
||||||
$parts = explode('@', $username);
|
|
||||||
$domain = last($parts);
|
|
||||||
$instance = InstanceService::getByDomain($domain);
|
|
||||||
|
|
||||||
if($instance) {
|
|
||||||
if($instance->banned == true) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
$wf = WebfingerUrl::generateWebfingerUrl($username);
|
|
||||||
$res = Http::head($wf);
|
|
||||||
return $res->ok();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$wf = WebfingerUrl::generateWebfingerUrl($username);
|
|
||||||
$res = Http::head($wf);
|
|
||||||
return $res->ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ class ImportService
|
||||||
if($userId > 999999) {
|
if($userId > 999999) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if($year < 9 || $year > 23) {
|
if($year < 9 || $year > (int) now()->addYear()->format('y')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if($month < 1 || $month > 12) {
|
if($month < 1 || $month > 12) {
|
||||||
|
|
|
@ -3,14 +3,13 @@
|
||||||
namespace App\Services\Status;
|
namespace App\Services\Status;
|
||||||
|
|
||||||
use App\Media;
|
use App\Media;
|
||||||
use App\ModLog;
|
|
||||||
use App\Status;
|
|
||||||
use App\Models\StatusEdit;
|
use App\Models\StatusEdit;
|
||||||
use Purify;
|
use App\ModLog;
|
||||||
use App\Util\Lexer\Autolink;
|
|
||||||
use App\Services\MediaService;
|
use App\Services\MediaService;
|
||||||
use App\Services\MediaStorageService;
|
use App\Services\MediaStorageService;
|
||||||
use App\Services\StatusService;
|
use App\Services\StatusService;
|
||||||
|
use App\Status;
|
||||||
|
use Purify;
|
||||||
|
|
||||||
class UpdateStatusService
|
class UpdateStatusService
|
||||||
{
|
{
|
||||||
|
@ -31,7 +30,9 @@ class UpdateStatusService
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$oids = $status->media()->orderBy('order')->pluck('id')->map(function($m) { return (string) $m; });
|
$oids = $status->media()->orderBy('order')->pluck('id')->map(function ($m) {
|
||||||
|
return (string) $m;
|
||||||
|
});
|
||||||
$nids = collect($attributes['media_ids']);
|
$nids = collect($attributes['media_ids']);
|
||||||
|
|
||||||
if ($oids->toArray() === $nids->toArray()) {
|
if ($oids->toArray() === $nids->toArray()) {
|
||||||
|
@ -64,15 +65,12 @@ class UpdateStatusService
|
||||||
if (isset($attributes['status'])) {
|
if (isset($attributes['status'])) {
|
||||||
$cleaned = Purify::clean($attributes['status']);
|
$cleaned = Purify::clean($attributes['status']);
|
||||||
$status->caption = $cleaned;
|
$status->caption = $cleaned;
|
||||||
$status->rendered = nl2br(Autolink::create()->autolink($cleaned));
|
|
||||||
} else {
|
} else {
|
||||||
$status->caption = null;
|
$status->caption = null;
|
||||||
$status->rendered = null;
|
|
||||||
}
|
}
|
||||||
if (isset($attributes['sensitive'])) {
|
if (isset($attributes['sensitive'])) {
|
||||||
if ($status->is_nsfw != (bool) $attributes['sensitive'] &&
|
if ($status->is_nsfw != (bool) $attributes['sensitive'] &&
|
||||||
(bool) $attributes['sensitive'] == false)
|
(bool) $attributes['sensitive'] == false) {
|
||||||
{
|
|
||||||
$exists = ModLog::whereObjectType('App\Status::class')
|
$exists = ModLog::whereObjectType('App\Status::class')
|
||||||
->whereObjectId($status->id)
|
->whereObjectId($status->id)
|
||||||
->whereAction('admin.status.moderate')
|
->whereAction('admin.status.moderate')
|
||||||
|
@ -114,7 +112,7 @@ class UpdateStatusService
|
||||||
'spoiler_text' => $status->cw_summary,
|
'spoiler_text' => $status->cw_summary,
|
||||||
'is_nsfw' => $status->is_nsfw,
|
'is_nsfw' => $status->is_nsfw,
|
||||||
'ordered_media_attachment_ids' => $status->media()->orderBy('order')->pluck('id')->toArray(),
|
'ordered_media_attachment_ids' => $status->media()->orderBy('order')->pluck('id')->toArray(),
|
||||||
'created_at' => $status->created_at
|
'created_at' => $status->created_at,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -131,7 +129,7 @@ class UpdateStatusService
|
||||||
'caption' => $cleaned,
|
'caption' => $cleaned,
|
||||||
'spoiler_text' => $spoiler_text,
|
'spoiler_text' => $spoiler_text,
|
||||||
'is_nsfw' => $sensitive,
|
'is_nsfw' => $sensitive,
|
||||||
'ordered_media_attachment_ids' => $mids
|
'ordered_media_attachment_ids' => $mids,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ use League\Fractal\Serializer\ArraySerializer;
|
||||||
|
|
||||||
class StatusService
|
class StatusService
|
||||||
{
|
{
|
||||||
const CACHE_KEY = 'pf:services:status:';
|
const CACHE_KEY = 'pf:services:status:v1.1:';
|
||||||
|
|
||||||
public static function key($id, $publicOnly = true)
|
public static function key($id, $publicOnly = true)
|
||||||
{
|
{
|
||||||
|
|
|
@ -308,46 +308,6 @@ class Status extends Model
|
||||||
return $this->comments()->orderBy('created_at', 'desc')->take(3);
|
return $this->comments()->orderBy('created_at', 'desc')->take(3);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toActivityPubObject()
|
|
||||||
{
|
|
||||||
if($this->local == false) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$profile = $this->profile;
|
|
||||||
$to = $this->scopeToAudience('to');
|
|
||||||
$cc = $this->scopeToAudience('cc');
|
|
||||||
return [
|
|
||||||
'@context' => 'https://www.w3.org/ns/activitystreams',
|
|
||||||
'id' => $this->permalink(),
|
|
||||||
'type' => 'Create',
|
|
||||||
'actor' => $profile->permalink(),
|
|
||||||
'published' => str_replace('+00:00', 'Z', $this->created_at->format(DATE_RFC3339_EXTENDED)),
|
|
||||||
'to' => $to,
|
|
||||||
'cc' => $cc,
|
|
||||||
'object' => [
|
|
||||||
'id' => $this->url(),
|
|
||||||
'type' => 'Note',
|
|
||||||
'summary' => null,
|
|
||||||
'inReplyTo' => null,
|
|
||||||
'published' => str_replace('+00:00', 'Z', $this->created_at->format(DATE_RFC3339_EXTENDED)),
|
|
||||||
'url' => $this->url(),
|
|
||||||
'attributedTo' => $this->profile->url(),
|
|
||||||
'to' => $to,
|
|
||||||
'cc' => $cc,
|
|
||||||
'sensitive' => (bool) $this->is_nsfw,
|
|
||||||
'content' => $this->rendered,
|
|
||||||
'attachment' => $this->media->map(function($media) {
|
|
||||||
return [
|
|
||||||
'type' => 'Document',
|
|
||||||
'mediaType' => $media->mime,
|
|
||||||
'url' => $media->url(),
|
|
||||||
'name' => null
|
|
||||||
];
|
|
||||||
})->toArray()
|
|
||||||
]
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function scopeToAudience($audience)
|
public function scopeToAudience($audience)
|
||||||
{
|
{
|
||||||
if(!in_array($audience, ['to', 'cc']) || $this->local == false) {
|
if(!in_array($audience, ['to', 'cc']) || $this->local == false) {
|
||||||
|
|
|
@ -2,14 +2,27 @@
|
||||||
|
|
||||||
namespace App\Transformer\ActivityPub;
|
namespace App\Transformer\ActivityPub;
|
||||||
|
|
||||||
use App\Status;
|
|
||||||
use League\Fractal;
|
|
||||||
use App\Services\MediaService;
|
use App\Services\MediaService;
|
||||||
|
use App\Services\StatusService;
|
||||||
|
use App\Status;
|
||||||
|
use App\Util\Lexer\Autolink;
|
||||||
|
use League\Fractal;
|
||||||
|
|
||||||
class StatusTransformer extends Fractal\TransformerAbstract
|
class StatusTransformer extends Fractal\TransformerAbstract
|
||||||
{
|
{
|
||||||
public function transform(Status $status)
|
public function transform(Status $status)
|
||||||
{
|
{
|
||||||
|
$content = $status->caption ? nl2br(Autolink::create()->autolink($status->caption)) : '';
|
||||||
|
|
||||||
|
$inReplyTo = null;
|
||||||
|
|
||||||
|
if ($status->in_reply_to_id) {
|
||||||
|
$reply = StatusService::get($status->in_reply_to_id, true);
|
||||||
|
if ($reply && isset($reply['url'])) {
|
||||||
|
$inReplyTo = $reply['url'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'@context' => [
|
'@context' => [
|
||||||
'https://www.w3.org/ns/activitystreams',
|
'https://www.w3.org/ns/activitystreams',
|
||||||
|
@ -22,30 +35,20 @@ class StatusTransformer extends Fractal\TransformerAbstract
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'id' => $status->url(),
|
'id' => $status->url(),
|
||||||
|
|
||||||
// TODO: handle other types
|
|
||||||
'type' => 'Note',
|
'type' => 'Note',
|
||||||
|
|
||||||
// XXX: CW Title
|
|
||||||
'summary' => null,
|
'summary' => null,
|
||||||
'content' => $status->rendered ?? $status->caption,
|
'content' => $content,
|
||||||
'inReplyTo' => null,
|
'inReplyTo' => $inReplyTo,
|
||||||
|
|
||||||
// TODO: fix date format
|
|
||||||
'published' => $status->created_at->toAtomString(),
|
'published' => $status->created_at->toAtomString(),
|
||||||
'url' => $status->url(),
|
'url' => $status->url(),
|
||||||
'attributedTo' => $status->profile->permalink(),
|
'attributedTo' => $status->profile->permalink(),
|
||||||
'to' => [
|
'to' => [
|
||||||
// TODO: handle proper scope
|
|
||||||
'https://www.w3.org/ns/activitystreams#Public',
|
'https://www.w3.org/ns/activitystreams#Public',
|
||||||
],
|
],
|
||||||
'cc' => [
|
'cc' => [
|
||||||
// TODO: add cc's
|
|
||||||
$status->profile->permalink('/followers'),
|
$status->profile->permalink('/followers'),
|
||||||
],
|
],
|
||||||
'sensitive' => (bool) $status->is_nsfw,
|
'sensitive' => (bool) $status->is_nsfw,
|
||||||
'atomUri' => $status->url(),
|
|
||||||
'inReplyToAtomUri' => null,
|
|
||||||
'attachment' => MediaService::activitypub($status->id),
|
'attachment' => MediaService::activitypub($status->id),
|
||||||
'tag' => [],
|
'tag' => [],
|
||||||
'location' => $status->place_id ? [
|
'location' => $status->place_id ? [
|
||||||
|
@ -53,7 +56,7 @@ class StatusTransformer extends Fractal\TransformerAbstract
|
||||||
'name' => $status->place->name,
|
'name' => $status->place->name,
|
||||||
'longitude' => $status->place->long,
|
'longitude' => $status->place->long,
|
||||||
'latitude' => $status->place->lat,
|
'latitude' => $status->place->lat,
|
||||||
'country' => $status->place->country
|
'country' => $status->place->country,
|
||||||
] : null,
|
] : null,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,11 @@
|
||||||
|
|
||||||
namespace App\Transformer\ActivityPub\Verb;
|
namespace App\Transformer\ActivityPub\Verb;
|
||||||
|
|
||||||
use App\Status;
|
|
||||||
use League\Fractal;
|
|
||||||
use App\Models\CustomEmoji;
|
use App\Models\CustomEmoji;
|
||||||
|
use App\Status;
|
||||||
|
use App\Util\Lexer\Autolink;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
use League\Fractal;
|
||||||
|
|
||||||
class CreateNote extends Fractal\TransformerAbstract
|
class CreateNote extends Fractal\TransformerAbstract
|
||||||
{
|
{
|
||||||
|
@ -16,10 +17,11 @@ class CreateNote extends Fractal\TransformerAbstract
|
||||||
$name = Str::startsWith($webfinger, '@') ?
|
$name = Str::startsWith($webfinger, '@') ?
|
||||||
$webfinger :
|
$webfinger :
|
||||||
'@'.$webfinger;
|
'@'.$webfinger;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'type' => 'Mention',
|
'type' => 'Mention',
|
||||||
'href' => $mention->permalink(),
|
'href' => $mention->permalink(),
|
||||||
'name' => $name
|
'name' => $name,
|
||||||
];
|
];
|
||||||
})->toArray();
|
})->toArray();
|
||||||
|
|
||||||
|
@ -33,7 +35,7 @@ class CreateNote extends Fractal\TransformerAbstract
|
||||||
$reply = [
|
$reply = [
|
||||||
'type' => 'Mention',
|
'type' => 'Mention',
|
||||||
'href' => $parent->permalink(),
|
'href' => $parent->permalink(),
|
||||||
'name' => $name
|
'name' => $name,
|
||||||
];
|
];
|
||||||
$mentions = array_merge($reply, $mentions);
|
$mentions = array_merge($reply, $mentions);
|
||||||
}
|
}
|
||||||
|
@ -50,6 +52,7 @@ class CreateNote extends Fractal\TransformerAbstract
|
||||||
$emojis = CustomEmoji::scan($status->caption, true) ?? [];
|
$emojis = CustomEmoji::scan($status->caption, true) ?? [];
|
||||||
$emoji = array_merge($emojis, $mentions);
|
$emoji = array_merge($emojis, $mentions);
|
||||||
$tags = array_merge($emoji, $hashtags);
|
$tags = array_merge($emoji, $hashtags);
|
||||||
|
$content = $status->caption ? nl2br(Autolink::create()->autolink($status->caption)) : "";
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'@context' => [
|
'@context' => [
|
||||||
|
@ -62,28 +65,28 @@ class CreateNote extends Fractal\TransformerAbstract
|
||||||
'pixelfed' => 'http://pixelfed.org/ns#',
|
'pixelfed' => 'http://pixelfed.org/ns#',
|
||||||
'commentsEnabled' => [
|
'commentsEnabled' => [
|
||||||
'@id' => 'pixelfed:commentsEnabled',
|
'@id' => 'pixelfed:commentsEnabled',
|
||||||
'@type' => 'schema:Boolean'
|
'@type' => 'schema:Boolean',
|
||||||
],
|
],
|
||||||
'capabilities' => [
|
'capabilities' => [
|
||||||
'@id' => 'pixelfed:capabilities',
|
'@id' => 'pixelfed:capabilities',
|
||||||
'@container' => '@set'
|
'@container' => '@set',
|
||||||
],
|
],
|
||||||
'announce' => [
|
'announce' => [
|
||||||
'@id' => 'pixelfed:canAnnounce',
|
'@id' => 'pixelfed:canAnnounce',
|
||||||
'@type' => '@id'
|
'@type' => '@id',
|
||||||
],
|
],
|
||||||
'like' => [
|
'like' => [
|
||||||
'@id' => 'pixelfed:canLike',
|
'@id' => 'pixelfed:canLike',
|
||||||
'@type' => '@id'
|
'@type' => '@id',
|
||||||
],
|
],
|
||||||
'reply' => [
|
'reply' => [
|
||||||
'@id' => 'pixelfed:canReply',
|
'@id' => 'pixelfed:canReply',
|
||||||
'@type' => '@id'
|
'@type' => '@id',
|
||||||
],
|
],
|
||||||
'toot' => 'http://joinmastodon.org/ns#',
|
'toot' => 'http://joinmastodon.org/ns#',
|
||||||
'Emoji' => 'toot:Emoji',
|
'Emoji' => 'toot:Emoji',
|
||||||
'blurhash' => 'toot:blurhash',
|
'blurhash' => 'toot:blurhash',
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
'id' => $status->permalink(),
|
'id' => $status->permalink(),
|
||||||
'type' => 'Create',
|
'type' => 'Create',
|
||||||
|
@ -95,7 +98,7 @@ class CreateNote extends Fractal\TransformerAbstract
|
||||||
'id' => $status->url(),
|
'id' => $status->url(),
|
||||||
'type' => 'Note',
|
'type' => 'Note',
|
||||||
'summary' => $status->is_nsfw ? $status->cw_summary : null,
|
'summary' => $status->is_nsfw ? $status->cw_summary : null,
|
||||||
'content' => $status->rendered ?? $status->caption,
|
'content' => $content,
|
||||||
'inReplyTo' => $status->in_reply_to_id ? $status->parent()->url() : null,
|
'inReplyTo' => $status->in_reply_to_id ? $status->parent()->url() : null,
|
||||||
'published' => $status->created_at->toAtomString(),
|
'published' => $status->created_at->toAtomString(),
|
||||||
'url' => $status->url(),
|
'url' => $status->url(),
|
||||||
|
@ -119,6 +122,7 @@ class CreateNote extends Fractal\TransformerAbstract
|
||||||
if ($media->height) {
|
if ($media->height) {
|
||||||
$res['height'] = $media->height;
|
$res['height'] = $media->height;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $res;
|
return $res;
|
||||||
})->toArray(),
|
})->toArray(),
|
||||||
'tag' => $tags,
|
'tag' => $tags,
|
||||||
|
@ -126,16 +130,16 @@ class CreateNote extends Fractal\TransformerAbstract
|
||||||
'capabilities' => [
|
'capabilities' => [
|
||||||
'announce' => 'https://www.w3.org/ns/activitystreams#Public',
|
'announce' => 'https://www.w3.org/ns/activitystreams#Public',
|
||||||
'like' => 'https://www.w3.org/ns/activitystreams#Public',
|
'like' => 'https://www.w3.org/ns/activitystreams#Public',
|
||||||
'reply' => $status->comments_disabled == true ? '[]' : 'https://www.w3.org/ns/activitystreams#Public'
|
'reply' => $status->comments_disabled == true ? '[]' : 'https://www.w3.org/ns/activitystreams#Public',
|
||||||
],
|
],
|
||||||
'location' => $status->place_id ? [
|
'location' => $status->place_id ? [
|
||||||
'type' => 'Place',
|
'type' => 'Place',
|
||||||
'name' => $status->place->name,
|
'name' => $status->place->name,
|
||||||
'longitude' => $status->place->long,
|
'longitude' => $status->place->long,
|
||||||
'latitude' => $status->place->lat,
|
'latitude' => $status->place->lat,
|
||||||
'country' => $status->place->country
|
'country' => $status->place->country,
|
||||||
] : null,
|
] : null,
|
||||||
]
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,11 @@
|
||||||
|
|
||||||
namespace App\Transformer\ActivityPub\Verb;
|
namespace App\Transformer\ActivityPub\Verb;
|
||||||
|
|
||||||
use App\Status;
|
|
||||||
use League\Fractal;
|
|
||||||
use App\Models\CustomEmoji;
|
use App\Models\CustomEmoji;
|
||||||
|
use App\Status;
|
||||||
|
use App\Util\Lexer\Autolink;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
use League\Fractal;
|
||||||
|
|
||||||
class Note extends Fractal\TransformerAbstract
|
class Note extends Fractal\TransformerAbstract
|
||||||
{
|
{
|
||||||
|
@ -17,10 +18,11 @@ class Note extends Fractal\TransformerAbstract
|
||||||
$name = Str::startsWith($webfinger, '@') ?
|
$name = Str::startsWith($webfinger, '@') ?
|
||||||
$webfinger :
|
$webfinger :
|
||||||
'@'.$webfinger;
|
'@'.$webfinger;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'type' => 'Mention',
|
'type' => 'Mention',
|
||||||
'href' => $mention->permalink(),
|
'href' => $mention->permalink(),
|
||||||
'name' => $name
|
'name' => $name,
|
||||||
];
|
];
|
||||||
})->toArray();
|
})->toArray();
|
||||||
|
|
||||||
|
@ -34,7 +36,7 @@ class Note extends Fractal\TransformerAbstract
|
||||||
$reply = [
|
$reply = [
|
||||||
'type' => 'Mention',
|
'type' => 'Mention',
|
||||||
'href' => $parent->permalink(),
|
'href' => $parent->permalink(),
|
||||||
'name' => $name
|
'name' => $name,
|
||||||
];
|
];
|
||||||
array_push($mentions, $reply);
|
array_push($mentions, $reply);
|
||||||
}
|
}
|
||||||
|
@ -51,6 +53,7 @@ class Note extends Fractal\TransformerAbstract
|
||||||
$emojis = CustomEmoji::scan($status->caption, true) ?? [];
|
$emojis = CustomEmoji::scan($status->caption, true) ?? [];
|
||||||
$emoji = array_merge($emojis, $mentions);
|
$emoji = array_merge($emojis, $mentions);
|
||||||
$tags = array_merge($emoji, $hashtags);
|
$tags = array_merge($emoji, $hashtags);
|
||||||
|
$content = $status->caption ? nl2br(Autolink::create()->autolink($status->caption)) : "";
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'@context' => [
|
'@context' => [
|
||||||
|
@ -63,33 +66,33 @@ class Note extends Fractal\TransformerAbstract
|
||||||
'pixelfed' => 'http://pixelfed.org/ns#',
|
'pixelfed' => 'http://pixelfed.org/ns#',
|
||||||
'commentsEnabled' => [
|
'commentsEnabled' => [
|
||||||
'@id' => 'pixelfed:commentsEnabled',
|
'@id' => 'pixelfed:commentsEnabled',
|
||||||
'@type' => 'schema:Boolean'
|
'@type' => 'schema:Boolean',
|
||||||
],
|
],
|
||||||
'capabilities' => [
|
'capabilities' => [
|
||||||
'@id' => 'pixelfed:capabilities',
|
'@id' => 'pixelfed:capabilities',
|
||||||
'@container' => '@set'
|
'@container' => '@set',
|
||||||
],
|
],
|
||||||
'announce' => [
|
'announce' => [
|
||||||
'@id' => 'pixelfed:canAnnounce',
|
'@id' => 'pixelfed:canAnnounce',
|
||||||
'@type' => '@id'
|
'@type' => '@id',
|
||||||
],
|
],
|
||||||
'like' => [
|
'like' => [
|
||||||
'@id' => 'pixelfed:canLike',
|
'@id' => 'pixelfed:canLike',
|
||||||
'@type' => '@id'
|
'@type' => '@id',
|
||||||
],
|
],
|
||||||
'reply' => [
|
'reply' => [
|
||||||
'@id' => 'pixelfed:canReply',
|
'@id' => 'pixelfed:canReply',
|
||||||
'@type' => '@id'
|
'@type' => '@id',
|
||||||
],
|
],
|
||||||
'toot' => 'http://joinmastodon.org/ns#',
|
'toot' => 'http://joinmastodon.org/ns#',
|
||||||
'Emoji' => 'toot:Emoji',
|
'Emoji' => 'toot:Emoji',
|
||||||
'blurhash' => 'toot:blurhash',
|
'blurhash' => 'toot:blurhash',
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
'id' => $status->url(),
|
'id' => $status->url(),
|
||||||
'type' => 'Note',
|
'type' => 'Note',
|
||||||
'summary' => $status->is_nsfw ? $status->cw_summary : null,
|
'summary' => $status->is_nsfw ? $status->cw_summary : null,
|
||||||
'content' => $status->rendered ?? $status->caption,
|
'content' => $content,
|
||||||
'inReplyTo' => $status->in_reply_to_id ? $status->parent()->url() : null,
|
'inReplyTo' => $status->in_reply_to_id ? $status->parent()->url() : null,
|
||||||
'published' => $status->created_at->toAtomString(),
|
'published' => $status->created_at->toAtomString(),
|
||||||
'url' => $status->url(),
|
'url' => $status->url(),
|
||||||
|
@ -113,6 +116,7 @@ class Note extends Fractal\TransformerAbstract
|
||||||
if ($media->height) {
|
if ($media->height) {
|
||||||
$res['height'] = $media->height;
|
$res['height'] = $media->height;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $res;
|
return $res;
|
||||||
})->toArray(),
|
})->toArray(),
|
||||||
'tag' => $tags,
|
'tag' => $tags,
|
||||||
|
@ -120,14 +124,14 @@ class Note extends Fractal\TransformerAbstract
|
||||||
'capabilities' => [
|
'capabilities' => [
|
||||||
'announce' => 'https://www.w3.org/ns/activitystreams#Public',
|
'announce' => 'https://www.w3.org/ns/activitystreams#Public',
|
||||||
'like' => 'https://www.w3.org/ns/activitystreams#Public',
|
'like' => 'https://www.w3.org/ns/activitystreams#Public',
|
||||||
'reply' => $status->comments_disabled == true ? '[]' : 'https://www.w3.org/ns/activitystreams#Public'
|
'reply' => $status->comments_disabled == true ? '[]' : 'https://www.w3.org/ns/activitystreams#Public',
|
||||||
],
|
],
|
||||||
'location' => $status->place_id ? [
|
'location' => $status->place_id ? [
|
||||||
'type' => 'Place',
|
'type' => 'Place',
|
||||||
'name' => $status->place->name,
|
'name' => $status->place->name,
|
||||||
'longitude' => $status->place->long,
|
'longitude' => $status->place->long,
|
||||||
'latitude' => $status->place->lat,
|
'latitude' => $status->place->lat,
|
||||||
'country' => $status->place->country
|
'country' => $status->place->country,
|
||||||
] : null,
|
] : null,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,9 @@
|
||||||
namespace App\Transformer\ActivityPub\Verb;
|
namespace App\Transformer\ActivityPub\Verb;
|
||||||
|
|
||||||
use App\Status;
|
use App\Status;
|
||||||
use League\Fractal;
|
use App\Util\Lexer\Autolink;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
use League\Fractal;
|
||||||
|
|
||||||
class Question extends Fractal\TransformerAbstract
|
class Question extends Fractal\TransformerAbstract
|
||||||
{
|
{
|
||||||
|
@ -15,10 +16,11 @@ class Question extends Fractal\TransformerAbstract
|
||||||
$name = Str::startsWith($webfinger, '@') ?
|
$name = Str::startsWith($webfinger, '@') ?
|
||||||
$webfinger :
|
$webfinger :
|
||||||
'@'.$webfinger;
|
'@'.$webfinger;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'type' => 'Mention',
|
'type' => 'Mention',
|
||||||
'href' => $mention->permalink(),
|
'href' => $mention->permalink(),
|
||||||
'name' => $name
|
'name' => $name,
|
||||||
];
|
];
|
||||||
})->toArray();
|
})->toArray();
|
||||||
|
|
||||||
|
@ -30,6 +32,7 @@ class Question extends Fractal\TransformerAbstract
|
||||||
];
|
];
|
||||||
})->toArray();
|
})->toArray();
|
||||||
$tags = array_merge($mentions, $hashtags);
|
$tags = array_merge($mentions, $hashtags);
|
||||||
|
$content = $status->caption ? Autolink::create()->autolink($status->caption) : null;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'@context' => [
|
'@context' => [
|
||||||
|
@ -42,32 +45,32 @@ class Question extends Fractal\TransformerAbstract
|
||||||
'pixelfed' => 'http://pixelfed.org/ns#',
|
'pixelfed' => 'http://pixelfed.org/ns#',
|
||||||
'commentsEnabled' => [
|
'commentsEnabled' => [
|
||||||
'@id' => 'pixelfed:commentsEnabled',
|
'@id' => 'pixelfed:commentsEnabled',
|
||||||
'@type' => 'schema:Boolean'
|
'@type' => 'schema:Boolean',
|
||||||
],
|
],
|
||||||
'capabilities' => [
|
'capabilities' => [
|
||||||
'@id' => 'pixelfed:capabilities',
|
'@id' => 'pixelfed:capabilities',
|
||||||
'@container' => '@set'
|
'@container' => '@set',
|
||||||
],
|
],
|
||||||
'announce' => [
|
'announce' => [
|
||||||
'@id' => 'pixelfed:canAnnounce',
|
'@id' => 'pixelfed:canAnnounce',
|
||||||
'@type' => '@id'
|
'@type' => '@id',
|
||||||
],
|
],
|
||||||
'like' => [
|
'like' => [
|
||||||
'@id' => 'pixelfed:canLike',
|
'@id' => 'pixelfed:canLike',
|
||||||
'@type' => '@id'
|
'@type' => '@id',
|
||||||
],
|
],
|
||||||
'reply' => [
|
'reply' => [
|
||||||
'@id' => 'pixelfed:canReply',
|
'@id' => 'pixelfed:canReply',
|
||||||
'@type' => '@id'
|
'@type' => '@id',
|
||||||
],
|
],
|
||||||
'toot' => 'http://joinmastodon.org/ns#',
|
'toot' => 'http://joinmastodon.org/ns#',
|
||||||
'Emoji' => 'toot:Emoji'
|
'Emoji' => 'toot:Emoji',
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
'id' => $status->url(),
|
'id' => $status->url(),
|
||||||
'type' => 'Question',
|
'type' => 'Question',
|
||||||
'summary' => null,
|
'summary' => null,
|
||||||
'content' => $status->rendered ?? $status->caption,
|
'content' => $content,
|
||||||
'inReplyTo' => $status->in_reply_to_id ? $status->parent()->url() : null,
|
'inReplyTo' => $status->in_reply_to_id ? $status->parent()->url() : null,
|
||||||
'published' => $status->created_at->toAtomString(),
|
'published' => $status->created_at->toAtomString(),
|
||||||
'url' => $status->url(),
|
'url' => $status->url(),
|
||||||
|
@ -81,14 +84,14 @@ class Question extends Fractal\TransformerAbstract
|
||||||
'capabilities' => [
|
'capabilities' => [
|
||||||
'announce' => 'https://www.w3.org/ns/activitystreams#Public',
|
'announce' => 'https://www.w3.org/ns/activitystreams#Public',
|
||||||
'like' => 'https://www.w3.org/ns/activitystreams#Public',
|
'like' => 'https://www.w3.org/ns/activitystreams#Public',
|
||||||
'reply' => $status->comments_disabled == true ? '[]' : 'https://www.w3.org/ns/activitystreams#Public'
|
'reply' => $status->comments_disabled == true ? '[]' : 'https://www.w3.org/ns/activitystreams#Public',
|
||||||
],
|
],
|
||||||
'location' => $status->place_id ? [
|
'location' => $status->place_id ? [
|
||||||
'type' => 'Place',
|
'type' => 'Place',
|
||||||
'name' => $status->place->name,
|
'name' => $status->place->name,
|
||||||
'longitude' => $status->place->long,
|
'longitude' => $status->place->long,
|
||||||
'latitude' => $status->place->lat,
|
'latitude' => $status->place->lat,
|
||||||
'country' => $status->place->country
|
'country' => $status->place->country,
|
||||||
] : null,
|
] : null,
|
||||||
'endTime' => $status->poll->expires_at->toAtomString(),
|
'endTime' => $status->poll->expires_at->toAtomString(),
|
||||||
'oneOf' => collect($status->poll->poll_options)->map(function ($option, $index) use ($status) {
|
'oneOf' => collect($status->poll->poll_options)->map(function ($option, $index) use ($status) {
|
||||||
|
@ -97,10 +100,10 @@ class Question extends Fractal\TransformerAbstract
|
||||||
'name' => $option,
|
'name' => $option,
|
||||||
'replies' => [
|
'replies' => [
|
||||||
'type' => 'Collection',
|
'type' => 'Collection',
|
||||||
'totalItems' => $status->poll->cached_tallies[$index]
|
'totalItems' => $status->poll->cached_tallies[$index],
|
||||||
]
|
],
|
||||||
];
|
];
|
||||||
})
|
}),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,11 @@
|
||||||
|
|
||||||
namespace App\Transformer\ActivityPub\Verb;
|
namespace App\Transformer\ActivityPub\Verb;
|
||||||
|
|
||||||
use App\Status;
|
|
||||||
use League\Fractal;
|
|
||||||
use App\Models\CustomEmoji;
|
use App\Models\CustomEmoji;
|
||||||
|
use App\Status;
|
||||||
|
use App\Util\Lexer\Autolink;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
use League\Fractal;
|
||||||
|
|
||||||
class UpdateNote extends Fractal\TransformerAbstract
|
class UpdateNote extends Fractal\TransformerAbstract
|
||||||
{
|
{
|
||||||
|
@ -16,10 +17,11 @@ class UpdateNote extends Fractal\TransformerAbstract
|
||||||
$name = Str::startsWith($webfinger, '@') ?
|
$name = Str::startsWith($webfinger, '@') ?
|
||||||
$webfinger :
|
$webfinger :
|
||||||
'@'.$webfinger;
|
'@'.$webfinger;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'type' => 'Mention',
|
'type' => 'Mention',
|
||||||
'href' => $mention->permalink(),
|
'href' => $mention->permalink(),
|
||||||
'name' => $name
|
'name' => $name,
|
||||||
];
|
];
|
||||||
})->toArray();
|
})->toArray();
|
||||||
|
|
||||||
|
@ -33,7 +35,7 @@ class UpdateNote extends Fractal\TransformerAbstract
|
||||||
$reply = [
|
$reply = [
|
||||||
'type' => 'Mention',
|
'type' => 'Mention',
|
||||||
'href' => $parent->permalink(),
|
'href' => $parent->permalink(),
|
||||||
'name' => $name
|
'name' => $name,
|
||||||
];
|
];
|
||||||
$mentions = array_merge($reply, $mentions);
|
$mentions = array_merge($reply, $mentions);
|
||||||
}
|
}
|
||||||
|
@ -51,6 +53,7 @@ class UpdateNote extends Fractal\TransformerAbstract
|
||||||
$emoji = array_merge($emojis, $mentions);
|
$emoji = array_merge($emojis, $mentions);
|
||||||
$tags = array_merge($emoji, $hashtags);
|
$tags = array_merge($emoji, $hashtags);
|
||||||
|
|
||||||
|
$content = $status->caption ? nl2br(Autolink::create()->autolink($status->caption)) : "";
|
||||||
$latestEdit = $status->edits()->latest()->first();
|
$latestEdit = $status->edits()->latest()->first();
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
@ -64,27 +67,27 @@ class UpdateNote extends Fractal\TransformerAbstract
|
||||||
'pixelfed' => 'http://pixelfed.org/ns#',
|
'pixelfed' => 'http://pixelfed.org/ns#',
|
||||||
'commentsEnabled' => [
|
'commentsEnabled' => [
|
||||||
'@id' => 'pixelfed:commentsEnabled',
|
'@id' => 'pixelfed:commentsEnabled',
|
||||||
'@type' => 'schema:Boolean'
|
'@type' => 'schema:Boolean',
|
||||||
],
|
],
|
||||||
'capabilities' => [
|
'capabilities' => [
|
||||||
'@id' => 'pixelfed:capabilities',
|
'@id' => 'pixelfed:capabilities',
|
||||||
'@container' => '@set'
|
'@container' => '@set',
|
||||||
],
|
],
|
||||||
'announce' => [
|
'announce' => [
|
||||||
'@id' => 'pixelfed:canAnnounce',
|
'@id' => 'pixelfed:canAnnounce',
|
||||||
'@type' => '@id'
|
'@type' => '@id',
|
||||||
],
|
],
|
||||||
'like' => [
|
'like' => [
|
||||||
'@id' => 'pixelfed:canLike',
|
'@id' => 'pixelfed:canLike',
|
||||||
'@type' => '@id'
|
'@type' => '@id',
|
||||||
],
|
],
|
||||||
'reply' => [
|
'reply' => [
|
||||||
'@id' => 'pixelfed:canReply',
|
'@id' => 'pixelfed:canReply',
|
||||||
'@type' => '@id'
|
'@type' => '@id',
|
||||||
],
|
],
|
||||||
'toot' => 'http://joinmastodon.org/ns#',
|
'toot' => 'http://joinmastodon.org/ns#',
|
||||||
'Emoji' => 'toot:Emoji'
|
'Emoji' => 'toot:Emoji',
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
'id' => $status->permalink('#updates/'.$latestEdit->id),
|
'id' => $status->permalink('#updates/'.$latestEdit->id),
|
||||||
'type' => 'Update',
|
'type' => 'Update',
|
||||||
|
@ -96,7 +99,7 @@ class UpdateNote extends Fractal\TransformerAbstract
|
||||||
'id' => $status->url(),
|
'id' => $status->url(),
|
||||||
'type' => 'Note',
|
'type' => 'Note',
|
||||||
'summary' => $status->is_nsfw ? $status->cw_summary : null,
|
'summary' => $status->is_nsfw ? $status->cw_summary : null,
|
||||||
'content' => $status->rendered ?? $status->caption,
|
'content' => $content,
|
||||||
'inReplyTo' => $status->in_reply_to_id ? $status->parent()->url() : null,
|
'inReplyTo' => $status->in_reply_to_id ? $status->parent()->url() : null,
|
||||||
'published' => $status->created_at->toAtomString(),
|
'published' => $status->created_at->toAtomString(),
|
||||||
'url' => $status->url(),
|
'url' => $status->url(),
|
||||||
|
@ -118,16 +121,16 @@ class UpdateNote extends Fractal\TransformerAbstract
|
||||||
'capabilities' => [
|
'capabilities' => [
|
||||||
'announce' => 'https://www.w3.org/ns/activitystreams#Public',
|
'announce' => 'https://www.w3.org/ns/activitystreams#Public',
|
||||||
'like' => 'https://www.w3.org/ns/activitystreams#Public',
|
'like' => 'https://www.w3.org/ns/activitystreams#Public',
|
||||||
'reply' => $status->comments_disabled == true ? '[]' : 'https://www.w3.org/ns/activitystreams#Public'
|
'reply' => $status->comments_disabled == true ? '[]' : 'https://www.w3.org/ns/activitystreams#Public',
|
||||||
],
|
],
|
||||||
'location' => $status->place_id ? [
|
'location' => $status->place_id ? [
|
||||||
'type' => 'Place',
|
'type' => 'Place',
|
||||||
'name' => $status->place->name,
|
'name' => $status->place->name,
|
||||||
'longitude' => $status->place->long,
|
'longitude' => $status->place->long,
|
||||||
'latitude' => $status->place->lat,
|
'latitude' => $status->place->lat,
|
||||||
'country' => $status->place->country
|
'country' => $status->place->country,
|
||||||
] : null,
|
] : null,
|
||||||
]
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,17 +2,19 @@
|
||||||
|
|
||||||
namespace App\Transformer\Api\Mastodon\v1;
|
namespace App\Transformer\Api\Mastodon\v1;
|
||||||
|
|
||||||
use App\Status;
|
|
||||||
use League\Fractal;
|
|
||||||
use Cache;
|
|
||||||
use App\Services\MediaService;
|
use App\Services\MediaService;
|
||||||
use App\Services\ProfileService;
|
use App\Services\ProfileService;
|
||||||
use App\Services\StatusHashtagService;
|
use App\Services\StatusHashtagService;
|
||||||
|
use App\Status;
|
||||||
|
use App\Util\Lexer\Autolink;
|
||||||
|
use League\Fractal;
|
||||||
|
|
||||||
class StatusTransformer extends Fractal\TransformerAbstract
|
class StatusTransformer extends Fractal\TransformerAbstract
|
||||||
{
|
{
|
||||||
public function transform(Status $status)
|
public function transform(Status $status)
|
||||||
{
|
{
|
||||||
|
$content = $status->caption ? nl2br(Autolink::create()->autolink($status->caption)) : "";
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'id' => (string) $status->id,
|
'id' => (string) $status->id,
|
||||||
'created_at' => $status->created_at->toJSON(),
|
'created_at' => $status->created_at->toJSON(),
|
||||||
|
@ -31,11 +33,11 @@ class StatusTransformer extends Fractal\TransformerAbstract
|
||||||
'favourited' => $status->liked(),
|
'favourited' => $status->liked(),
|
||||||
'muted' => false,
|
'muted' => false,
|
||||||
'bookmarked' => false,
|
'bookmarked' => false,
|
||||||
'content' => $status->rendered ?? $status->caption ?? '',
|
'content' => $content,
|
||||||
'reblog' => null,
|
'reblog' => null,
|
||||||
'application' => [
|
'application' => [
|
||||||
'name' => 'web',
|
'name' => 'web',
|
||||||
'website' => null
|
'website' => null,
|
||||||
],
|
],
|
||||||
'mentions' => [],
|
'mentions' => [],
|
||||||
'emojis' => [],
|
'emojis' => [],
|
||||||
|
|
|
@ -2,21 +2,20 @@
|
||||||
|
|
||||||
namespace App\Transformer\Api;
|
namespace App\Transformer\Api;
|
||||||
|
|
||||||
use App\Status;
|
use App\Models\CustomEmoji;
|
||||||
use League\Fractal;
|
|
||||||
use Cache;
|
|
||||||
use App\Services\AccountService;
|
use App\Services\AccountService;
|
||||||
use App\Services\HashidService;
|
use App\Services\HashidService;
|
||||||
use App\Services\LikeService;
|
use App\Services\LikeService;
|
||||||
use App\Services\MediaService;
|
use App\Services\MediaService;
|
||||||
use App\Services\MediaTagService;
|
use App\Services\MediaTagService;
|
||||||
use App\Services\StatusService;
|
use App\Services\PollService;
|
||||||
use App\Services\StatusHashtagService;
|
use App\Services\StatusHashtagService;
|
||||||
use App\Services\StatusLabelService;
|
use App\Services\StatusLabelService;
|
||||||
use App\Services\StatusMentionService;
|
use App\Services\StatusMentionService;
|
||||||
use App\Services\PollService;
|
use App\Services\StatusService;
|
||||||
use App\Models\CustomEmoji;
|
use App\Status;
|
||||||
use App\Util\Lexer\Autolink;
|
use App\Util\Lexer\Autolink;
|
||||||
|
use League\Fractal;
|
||||||
|
|
||||||
class StatusStatelessTransformer extends Fractal\TransformerAbstract
|
class StatusStatelessTransformer extends Fractal\TransformerAbstract
|
||||||
{
|
{
|
||||||
|
@ -24,9 +23,7 @@ class StatusStatelessTransformer extends Fractal\TransformerAbstract
|
||||||
{
|
{
|
||||||
$taggedPeople = MediaTagService::get($status->id);
|
$taggedPeople = MediaTagService::get($status->id);
|
||||||
$poll = $status->type === 'poll' ? PollService::get($status->id) : null;
|
$poll = $status->type === 'poll' ? PollService::get($status->id) : null;
|
||||||
$rendered = config('exp.autolink') ?
|
$rendered = $status->caption ? nl2br(Autolink::create()->autolink($status->caption)) : "";
|
||||||
( $status->caption ? Autolink::create()->autolink($status->caption) : '' ) :
|
|
||||||
( $status->rendered ?? $status->caption );
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'_v' => 1,
|
'_v' => 1,
|
||||||
|
@ -52,7 +49,7 @@ class StatusStatelessTransformer extends Fractal\TransformerAbstract
|
||||||
'visibility' => $status->scope ?? $status->visibility,
|
'visibility' => $status->scope ?? $status->visibility,
|
||||||
'application' => [
|
'application' => [
|
||||||
'name' => 'web',
|
'name' => 'web',
|
||||||
'website' => null
|
'website' => null,
|
||||||
],
|
],
|
||||||
'language' => null,
|
'language' => null,
|
||||||
'mentions' => StatusMentionService::get($status->id),
|
'mentions' => StatusMentionService::get($status->id),
|
||||||
|
|
|
@ -2,24 +2,21 @@
|
||||||
|
|
||||||
namespace App\Transformer\Api;
|
namespace App\Transformer\Api;
|
||||||
|
|
||||||
use App\Like;
|
use App\Models\CustomEmoji;
|
||||||
use App\Status;
|
use App\Services\BookmarkService;
|
||||||
use League\Fractal;
|
|
||||||
use Cache;
|
|
||||||
use App\Services\HashidService;
|
use App\Services\HashidService;
|
||||||
use App\Services\LikeService;
|
use App\Services\LikeService;
|
||||||
use App\Services\MediaService;
|
use App\Services\MediaService;
|
||||||
use App\Services\MediaTagService;
|
use App\Services\MediaTagService;
|
||||||
use App\Services\StatusService;
|
use App\Services\PollService;
|
||||||
|
use App\Services\ProfileService;
|
||||||
use App\Services\StatusHashtagService;
|
use App\Services\StatusHashtagService;
|
||||||
use App\Services\StatusLabelService;
|
use App\Services\StatusLabelService;
|
||||||
use App\Services\StatusMentionService;
|
use App\Services\StatusMentionService;
|
||||||
use App\Services\ProfileService;
|
use App\Services\StatusService;
|
||||||
use Illuminate\Support\Str;
|
use App\Status;
|
||||||
use App\Services\PollService;
|
|
||||||
use App\Models\CustomEmoji;
|
|
||||||
use App\Services\BookmarkService;
|
|
||||||
use App\Util\Lexer\Autolink;
|
use App\Util\Lexer\Autolink;
|
||||||
|
use League\Fractal;
|
||||||
|
|
||||||
class StatusTransformer extends Fractal\TransformerAbstract
|
class StatusTransformer extends Fractal\TransformerAbstract
|
||||||
{
|
{
|
||||||
|
@ -28,9 +25,7 @@ class StatusTransformer extends Fractal\TransformerAbstract
|
||||||
$pid = request()->user()->profile_id;
|
$pid = request()->user()->profile_id;
|
||||||
$taggedPeople = MediaTagService::get($status->id);
|
$taggedPeople = MediaTagService::get($status->id);
|
||||||
$poll = $status->type === 'poll' ? PollService::get($status->id, $pid) : null;
|
$poll = $status->type === 'poll' ? PollService::get($status->id, $pid) : null;
|
||||||
$rendered = config('exp.autolink') ?
|
$content = $status->caption ? nl2br(Autolink::create()->autolink($status->caption)) : '';
|
||||||
( $status->caption ? Autolink::create()->autolink($status->caption) : '' ) :
|
|
||||||
( $status->rendered ?? $status->caption );
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'_v' => 1,
|
'_v' => 1,
|
||||||
|
@ -38,10 +33,10 @@ class StatusTransformer extends Fractal\TransformerAbstract
|
||||||
'shortcode' => HashidService::encode($status->id),
|
'shortcode' => HashidService::encode($status->id),
|
||||||
'uri' => $status->url(),
|
'uri' => $status->url(),
|
||||||
'url' => $status->url(),
|
'url' => $status->url(),
|
||||||
'in_reply_to_id' => (string) $status->in_reply_to_id,
|
'in_reply_to_id' => $status->in_reply_to_id ? (string) $status->in_reply_to_id : null,
|
||||||
'in_reply_to_account_id' => (string) $status->in_reply_to_profile_id,
|
'in_reply_to_account_id' => $status->in_reply_to_profile_id ? (string) $status->in_reply_to_profile_id : null,
|
||||||
'reblog' => $status->reblog_of_id ? StatusService::get($status->reblog_of_id) : null,
|
'reblog' => $status->reblog_of_id ? StatusService::get($status->reblog_of_id) : null,
|
||||||
'content' => $rendered,
|
'content' => $content,
|
||||||
'content_text' => $status->caption,
|
'content_text' => $status->caption,
|
||||||
'created_at' => str_replace('+00:00', 'Z', $status->created_at->format(DATE_RFC3339_EXTENDED)),
|
'created_at' => str_replace('+00:00', 'Z', $status->created_at->format(DATE_RFC3339_EXTENDED)),
|
||||||
'emojis' => CustomEmoji::scan($status->caption),
|
'emojis' => CustomEmoji::scan($status->caption),
|
||||||
|
@ -55,7 +50,7 @@ class StatusTransformer extends Fractal\TransformerAbstract
|
||||||
'visibility' => $status->scope ?? $status->visibility,
|
'visibility' => $status->scope ?? $status->visibility,
|
||||||
'application' => [
|
'application' => [
|
||||||
'name' => 'web',
|
'name' => 'web',
|
||||||
'website' => null
|
'website' => null,
|
||||||
],
|
],
|
||||||
'language' => null,
|
'language' => null,
|
||||||
'mentions' => StatusMentionService::get($status->id),
|
'mentions' => StatusMentionService::get($status->id),
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -71,9 +71,14 @@ class HttpSignature
|
||||||
public static function instanceActorSign($url, $body = false, $addlHeaders = [], $method = 'post')
|
public static function instanceActorSign($url, $body = false, $addlHeaders = [], $method = 'post')
|
||||||
{
|
{
|
||||||
$keyId = config('app.url').'/i/actor#main-key';
|
$keyId = config('app.url').'/i/actor#main-key';
|
||||||
|
if(config_cache('database.default') === 'mysql') {
|
||||||
$privateKey = Cache::rememberForever(InstanceActor::PKI_PRIVATE, function () {
|
$privateKey = Cache::rememberForever(InstanceActor::PKI_PRIVATE, function () {
|
||||||
return InstanceActor::first()->private_key;
|
return InstanceActor::first()->private_key;
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
$privateKey = InstanceActor::first()?->private_key;
|
||||||
|
}
|
||||||
|
abort_if(!$privateKey || empty($privateKey), 400, 'Missing instance actor key, please run php artisan instance:actor');
|
||||||
if ($body) {
|
if ($body) {
|
||||||
$digest = self::_digest($body);
|
$digest = self::_digest($body);
|
||||||
}
|
}
|
||||||
|
|
|
@ -417,8 +417,8 @@ class Inbox
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$msg = $activity['content'];
|
$msg = Purify::clean($activity['content']);
|
||||||
$msgText = strip_tags($activity['content']);
|
$msgText = strip_tags($msg);
|
||||||
|
|
||||||
if (Str::startsWith($msgText, '@'.$profile->username)) {
|
if (Str::startsWith($msgText, '@'.$profile->username)) {
|
||||||
$len = strlen('@'.$profile->username);
|
$len = strlen('@'.$profile->username);
|
||||||
|
@ -438,7 +438,6 @@ class Inbox
|
||||||
$status = new Status;
|
$status = new Status;
|
||||||
$status->profile_id = $actor->id;
|
$status->profile_id = $actor->id;
|
||||||
$status->caption = $msgText;
|
$status->caption = $msgText;
|
||||||
$status->rendered = $msg;
|
|
||||||
$status->visibility = 'direct';
|
$status->visibility = 'direct';
|
||||||
$status->scope = 'direct';
|
$status->scope = 'direct';
|
||||||
$status->url = $activity['id'];
|
$status->url = $activity['id'];
|
||||||
|
@ -1081,7 +1080,6 @@ class Inbox
|
||||||
$status->uri = $url;
|
$status->uri = $url;
|
||||||
$status->object_url = $url;
|
$status->object_url = $url;
|
||||||
$status->caption = $text;
|
$status->caption = $text;
|
||||||
$status->rendered = $text;
|
|
||||||
$status->scope = 'direct';
|
$status->scope = 'direct';
|
||||||
$status->visibility = 'direct';
|
$status->visibility = 'direct';
|
||||||
$status->in_reply_to_profile_id = $story->profile_id;
|
$status->in_reply_to_profile_id = $story->profile_id;
|
||||||
|
@ -1199,7 +1197,6 @@ class Inbox
|
||||||
$status->profile_id = $actorProfile->id;
|
$status->profile_id = $actorProfile->id;
|
||||||
$status->type = 'story:reply';
|
$status->type = 'story:reply';
|
||||||
$status->caption = $text;
|
$status->caption = $text;
|
||||||
$status->rendered = $text;
|
|
||||||
$status->url = $url;
|
$status->url = $url;
|
||||||
$status->uri = $url;
|
$status->uri = $url;
|
||||||
$status->object_url = $url;
|
$status->object_url = $url;
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
"laravel/helpers": "^1.1",
|
"laravel/helpers": "^1.1",
|
||||||
"laravel/horizon": "^5.0",
|
"laravel/horizon": "^5.0",
|
||||||
"laravel/passport": "^12.0",
|
"laravel/passport": "^12.0",
|
||||||
|
"laravel/pulse": "^1.3",
|
||||||
"laravel/tinker": "^2.9",
|
"laravel/tinker": "^2.9",
|
||||||
"laravel/ui": "^4.2",
|
"laravel/ui": "^4.2",
|
||||||
"league/flysystem-aws-s3-v3": "^3.0",
|
"league/flysystem-aws-s3-v3": "^3.0",
|
||||||
|
|
1294
composer.lock
generated
1294
composer.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -85,11 +85,29 @@ return [
|
||||||
'database' => env('REDIS_DATABASE', 0),
|
'database' => env('REDIS_DATABASE', 0),
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'session' => [
|
||||||
|
'scheme' => env('REDIS_SCHEME', 'tcp'),
|
||||||
|
'path' => env('REDIS_PATH'),
|
||||||
|
'host' => env('REDIS_HOST', '127.0.0.1'),
|
||||||
|
'password' => env('REDIS_PASSWORD', null),
|
||||||
|
'port' => env('REDIS_PORT', 6379),
|
||||||
|
'database' => env('REDIS_DATABASE_SESSION', 1),
|
||||||
|
],
|
||||||
|
|
||||||
|
'pulse' => [
|
||||||
|
'scheme' => env('REDIS_SCHEME', 'tcp'),
|
||||||
|
'path' => env('REDIS_PATH'),
|
||||||
|
'host' => env('REDIS_HOST', '127.0.0.1'),
|
||||||
|
'password' => env('REDIS_PASSWORD', null),
|
||||||
|
'port' => env('REDIS_PORT', 6379),
|
||||||
|
'database' => env('REDIS_DATABASE_PULSE', 2),
|
||||||
|
],
|
||||||
|
|
||||||
],
|
],
|
||||||
|
|
||||||
'redis:session' => [
|
'redis:session' => [
|
||||||
'driver' => 'redis',
|
'driver' => 'redis',
|
||||||
'connection' => 'default',
|
'connection' => 'session',
|
||||||
'prefix' => 'pf_session',
|
'prefix' => 'pf_session',
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|
|
@ -143,6 +143,24 @@ return [
|
||||||
'database' => env('REDIS_DATABASE', 0),
|
'database' => env('REDIS_DATABASE', 0),
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'session' => [
|
||||||
|
'scheme' => env('REDIS_SCHEME', 'tcp'),
|
||||||
|
'path' => env('REDIS_PATH'),
|
||||||
|
'host' => env('REDIS_HOST', '127.0.0.1'),
|
||||||
|
'password' => env('REDIS_PASSWORD', null),
|
||||||
|
'port' => env('REDIS_PORT', 6379),
|
||||||
|
'database' => env('REDIS_DATABASE_SESSION', 1),
|
||||||
|
],
|
||||||
|
|
||||||
|
'pulse' => [
|
||||||
|
'scheme' => env('REDIS_SCHEME', 'tcp'),
|
||||||
|
'path' => env('REDIS_PATH'),
|
||||||
|
'host' => env('REDIS_HOST', '127.0.0.1'),
|
||||||
|
'password' => env('REDIS_PASSWORD', null),
|
||||||
|
'port' => env('REDIS_PORT', 6379),
|
||||||
|
'database' => env('REDIS_DATABASE_PULSE', 2),
|
||||||
|
],
|
||||||
|
|
||||||
],
|
],
|
||||||
|
|
||||||
'dbal' => [
|
'dbal' => [
|
||||||
|
|
|
@ -24,6 +24,10 @@ return [
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'image_optimize' => [
|
||||||
|
'catch_unoptimized_media_hour_limit' => env('PF_CATCHUNOPTIMIZEDMEDIA', false),
|
||||||
|
],
|
||||||
|
|
||||||
'hls' => [
|
'hls' => [
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
236
config/pulse.php
Normal file
236
config/pulse.php
Normal file
|
@ -0,0 +1,236 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Laravel\Pulse\Http\Middleware\Authorize;
|
||||||
|
use Laravel\Pulse\Pulse;
|
||||||
|
use Laravel\Pulse\Recorders;
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Pulse Domain
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This is the subdomain which the Pulse dashboard will be accessible from.
|
||||||
|
| When set to null, the dashboard will reside under the same domain as
|
||||||
|
| the application. Remember to configure your DNS entries correctly.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'domain' => env('PULSE_DOMAIN'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Pulse Path
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This is the path which the Pulse dashboard will be accessible from. Feel
|
||||||
|
| free to change this path to anything you'd like. Note that this won't
|
||||||
|
| affect the path of the internal API that is never exposed to users.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'path' => env('PULSE_PATH', 'pulse'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Pulse Master Switch
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This configuration option may be used to completely disable all Pulse
|
||||||
|
| data recorders regardless of their individual configurations. This
|
||||||
|
| provides a single option to quickly disable all Pulse recording.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'enabled' => env('PULSE_ENABLED', false),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Pulse Storage Driver
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This configuration option determines which storage driver will be used
|
||||||
|
| while storing entries from Pulse's recorders. In addition, you also
|
||||||
|
| may provide any options to configure the selected storage driver.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'storage' => [
|
||||||
|
'driver' => env('PULSE_STORAGE_DRIVER', 'database'),
|
||||||
|
|
||||||
|
'trim' => [
|
||||||
|
'keep' => env('PULSE_STORAGE_KEEP', '7 days'),
|
||||||
|
],
|
||||||
|
|
||||||
|
'database' => [
|
||||||
|
'connection' => env('PULSE_DB_CONNECTION'),
|
||||||
|
'chunk' => 1000,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Pulse Ingest Driver
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This configuration options determines the ingest driver that will be used
|
||||||
|
| to capture entries from Pulse's recorders. Ingest drivers are great to
|
||||||
|
| free up your request workers quickly by offloading the data storage.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'ingest' => [
|
||||||
|
'driver' => env('PULSE_INGEST_DRIVER', 'storage'),
|
||||||
|
|
||||||
|
'buffer' => env('PULSE_INGEST_BUFFER', 5_000),
|
||||||
|
|
||||||
|
'trim' => [
|
||||||
|
'lottery' => [1, 1_000],
|
||||||
|
'keep' => env('PULSE_INGEST_KEEP', '7 days'),
|
||||||
|
],
|
||||||
|
|
||||||
|
'redis' => [
|
||||||
|
'connection' => env('PULSE_REDIS_CONNECTION'),
|
||||||
|
'chunk' => 1000,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Pulse Cache Driver
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| This configuration option determines the cache driver that will be used
|
||||||
|
| for various tasks, including caching dashboard results, establishing
|
||||||
|
| locks for events that should only occur on one server and signals.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'cache' => env('PULSE_CACHE_DRIVER'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Pulse Route Middleware
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| These middleware will be assigned to every Pulse route, giving you the
|
||||||
|
| chance to add your own middleware to this list or change any of the
|
||||||
|
| existing middleware. Of course, reasonable defaults are provided.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'middleware' => [
|
||||||
|
'web',
|
||||||
|
Authorize::class,
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Pulse Recorders
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| The following array lists the "recorders" that will be registered with
|
||||||
|
| Pulse, along with their configuration. Recorders gather application
|
||||||
|
| event data from requests and tasks to pass to your ingest driver.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'recorders' => [
|
||||||
|
Recorders\CacheInteractions::class => [
|
||||||
|
'enabled' => env('PULSE_CACHE_INTERACTIONS_ENABLED', true),
|
||||||
|
'sample_rate' => env('PULSE_CACHE_INTERACTIONS_SAMPLE_RATE', 1),
|
||||||
|
'ignore' => [
|
||||||
|
...Pulse::defaultVendorCacheKeys(),
|
||||||
|
],
|
||||||
|
'groups' => [
|
||||||
|
'/^job-exceptions:.*/' => 'job-exceptions:*',
|
||||||
|
// '/:\d+/' => ':*',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
Recorders\Exceptions::class => [
|
||||||
|
'enabled' => env('PULSE_EXCEPTIONS_ENABLED', true),
|
||||||
|
'sample_rate' => env('PULSE_EXCEPTIONS_SAMPLE_RATE', 1),
|
||||||
|
'location' => env('PULSE_EXCEPTIONS_LOCATION', true),
|
||||||
|
'ignore' => [
|
||||||
|
// '/^Package\\\\Exceptions\\\\/',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
Recorders\Queues::class => [
|
||||||
|
'enabled' => env('PULSE_QUEUES_ENABLED', true),
|
||||||
|
'sample_rate' => env('PULSE_QUEUES_SAMPLE_RATE', 1),
|
||||||
|
'ignore' => [
|
||||||
|
// '/^Package\\\\Jobs\\\\/',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
Recorders\Servers::class => [
|
||||||
|
'server_name' => env('PULSE_SERVER_NAME', gethostname()),
|
||||||
|
'directories' => explode(':', env('PULSE_SERVER_DIRECTORIES', '/')),
|
||||||
|
],
|
||||||
|
|
||||||
|
Recorders\SlowJobs::class => [
|
||||||
|
'enabled' => env('PULSE_SLOW_JOBS_ENABLED', true),
|
||||||
|
'sample_rate' => env('PULSE_SLOW_JOBS_SAMPLE_RATE', 1),
|
||||||
|
'threshold' => env('PULSE_SLOW_JOBS_THRESHOLD', 1000),
|
||||||
|
'ignore' => [
|
||||||
|
// '/^Package\\\\Jobs\\\\/',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
Recorders\SlowOutgoingRequests::class => [
|
||||||
|
'enabled' => env('PULSE_SLOW_OUTGOING_REQUESTS_ENABLED', true),
|
||||||
|
'sample_rate' => env('PULSE_SLOW_OUTGOING_REQUESTS_SAMPLE_RATE', 1),
|
||||||
|
'threshold' => env('PULSE_SLOW_OUTGOING_REQUESTS_THRESHOLD', 1000),
|
||||||
|
'ignore' => [
|
||||||
|
// '#^http://127\.0\.0\.1:13714#', // Inertia SSR...
|
||||||
|
],
|
||||||
|
'groups' => [
|
||||||
|
// '#^https://api\.github\.com/repos/.*$#' => 'api.github.com/repos/*',
|
||||||
|
// '#^https?://([^/]*).*$#' => '\1',
|
||||||
|
// '#/\d+#' => '/*',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
Recorders\SlowQueries::class => [
|
||||||
|
'enabled' => env('PULSE_SLOW_QUERIES_ENABLED', true),
|
||||||
|
'sample_rate' => env('PULSE_SLOW_QUERIES_SAMPLE_RATE', 1),
|
||||||
|
'threshold' => env('PULSE_SLOW_QUERIES_THRESHOLD', 1000),
|
||||||
|
'location' => env('PULSE_SLOW_QUERIES_LOCATION', true),
|
||||||
|
'max_query_length' => env('PULSE_SLOW_QUERIES_MAX_QUERY_LENGTH'),
|
||||||
|
'ignore' => [
|
||||||
|
'/(["`])pulse_[\w]+?\1/', // Pulse tables...
|
||||||
|
'/(["`])telescope_[\w]+?\1/', // Telescope tables...
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
Recorders\SlowRequests::class => [
|
||||||
|
'enabled' => env('PULSE_SLOW_REQUESTS_ENABLED', true),
|
||||||
|
'sample_rate' => env('PULSE_SLOW_REQUESTS_SAMPLE_RATE', 1),
|
||||||
|
'threshold' => env('PULSE_SLOW_REQUESTS_THRESHOLD', 1000),
|
||||||
|
'ignore' => [
|
||||||
|
'#^/'.env('PULSE_PATH', 'pulse').'$#', // Pulse dashboard...
|
||||||
|
'#^/telescope#', // Telescope dashboard...
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
Recorders\UserJobs::class => [
|
||||||
|
'enabled' => env('PULSE_USER_JOBS_ENABLED', true),
|
||||||
|
'sample_rate' => env('PULSE_USER_JOBS_SAMPLE_RATE', 1),
|
||||||
|
'ignore' => [
|
||||||
|
// '/^Package\\\\Jobs\\\\/',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
Recorders\UserRequests::class => [
|
||||||
|
'enabled' => env('PULSE_USER_REQUESTS_ENABLED', true),
|
||||||
|
'sample_rate' => env('PULSE_USER_REQUESTS_SAMPLE_RATE', 1),
|
||||||
|
'ignore' => [
|
||||||
|
'#^/'.env('PULSE_PATH', 'pulse').'$#', // Pulse dashboard...
|
||||||
|
'#^/telescope#', // Telescope dashboard...
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
|
@ -17,6 +17,8 @@ return [
|
||||||
'mailgun' => [
|
'mailgun' => [
|
||||||
'domain' => env('MAILGUN_DOMAIN'),
|
'domain' => env('MAILGUN_DOMAIN'),
|
||||||
'secret' => env('MAILGUN_SECRET'),
|
'secret' => env('MAILGUN_SECRET'),
|
||||||
|
'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'),
|
||||||
|
'scheme' => 'https',
|
||||||
],
|
],
|
||||||
|
|
||||||
'ses' => [
|
'ses' => [
|
||||||
|
|
|
@ -19,7 +19,7 @@ class CreateDirectMessagesTable extends Migration
|
||||||
$table->bigInteger('from_id')->unsigned()->index();
|
$table->bigInteger('from_id')->unsigned()->index();
|
||||||
$table->string('from_profile_ids')->nullable();
|
$table->string('from_profile_ids')->nullable();
|
||||||
$table->boolean('group_message')->default(false);
|
$table->boolean('group_message')->default(false);
|
||||||
$table->bigInteger('status_id')->unsigned()->integer();
|
$table->bigInteger('status_id')->unsigned();
|
||||||
$table->unique(['to_id', 'from_id', 'status_id']);
|
$table->unique(['to_id', 'from_id', 'status_id']);
|
||||||
$table->timestamp('read_at')->nullable();
|
$table->timestamp('read_at')->nullable();
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Laravel\Pulse\Support\PulseMigration;
|
||||||
|
|
||||||
|
return new class extends PulseMigration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
if (! $this->shouldRun()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Schema::create('pulse_values', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->unsignedInteger('timestamp');
|
||||||
|
$table->string('type');
|
||||||
|
$table->mediumText('key');
|
||||||
|
match ($this->driver()) {
|
||||||
|
'mariadb', 'mysql' => $table->char('key_hash', 16)->charset('binary')->virtualAs('unhex(md5(`key`))'),
|
||||||
|
'pgsql' => $table->uuid('key_hash')->storedAs('md5("key")::uuid'),
|
||||||
|
'sqlite' => $table->string('key_hash'),
|
||||||
|
};
|
||||||
|
$table->mediumText('value');
|
||||||
|
|
||||||
|
$table->index('timestamp'); // For trimming...
|
||||||
|
$table->index('type'); // For fast lookups and purging...
|
||||||
|
$table->unique(['type', 'key_hash']); // For data integrity and upserts...
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create('pulse_entries', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->unsignedInteger('timestamp');
|
||||||
|
$table->string('type');
|
||||||
|
$table->mediumText('key');
|
||||||
|
match ($this->driver()) {
|
||||||
|
'mariadb', 'mysql' => $table->char('key_hash', 16)->charset('binary')->virtualAs('unhex(md5(`key`))'),
|
||||||
|
'pgsql' => $table->uuid('key_hash')->storedAs('md5("key")::uuid'),
|
||||||
|
'sqlite' => $table->string('key_hash'),
|
||||||
|
};
|
||||||
|
$table->bigInteger('value')->nullable();
|
||||||
|
|
||||||
|
$table->index('timestamp'); // For trimming...
|
||||||
|
$table->index('type'); // For purging...
|
||||||
|
$table->index('key_hash'); // For mapping...
|
||||||
|
$table->index(['timestamp', 'type', 'key_hash', 'value']); // For aggregate queries...
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create('pulse_aggregates', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->unsignedInteger('bucket');
|
||||||
|
$table->unsignedMediumInteger('period');
|
||||||
|
$table->string('type');
|
||||||
|
$table->mediumText('key');
|
||||||
|
match ($this->driver()) {
|
||||||
|
'mariadb', 'mysql' => $table->char('key_hash', 16)->charset('binary')->virtualAs('unhex(md5(`key`))'),
|
||||||
|
'pgsql' => $table->uuid('key_hash')->storedAs('md5("key")::uuid'),
|
||||||
|
'sqlite' => $table->string('key_hash'),
|
||||||
|
};
|
||||||
|
$table->string('aggregate');
|
||||||
|
$table->decimal('value', 20, 2);
|
||||||
|
$table->unsignedInteger('count')->nullable();
|
||||||
|
|
||||||
|
$table->unique(['bucket', 'period', 'type', 'aggregate', 'key_hash']); // Force "on duplicate update"...
|
||||||
|
$table->index(['period', 'bucket']); // For trimming...
|
||||||
|
$table->index('type'); // For purging...
|
||||||
|
$table->index(['period', 'type', 'aggregate', 'bucket']); // For aggregate queries...
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('pulse_values');
|
||||||
|
Schema::dropIfExists('pulse_entries');
|
||||||
|
Schema::dropIfExists('pulse_aggregates');
|
||||||
|
}
|
||||||
|
};
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
use Illuminate\Database\Migrations\Migration;
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Schema;
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
return new class extends Migration
|
return new class extends Migration
|
||||||
|
@ -12,6 +13,9 @@ return new class extends Migration
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::table('group_posts', function (Blueprint $table) {
|
Schema::table('group_posts', function (Blueprint $table) {
|
||||||
|
if (DB::getDriverName() === 'sqlite') {
|
||||||
|
$table->dropUnique(['status_id']);
|
||||||
|
}
|
||||||
$table->dropColumn('status_id');
|
$table->dropColumn('status_id');
|
||||||
$table->dropColumn('reply_child_id');
|
$table->dropColumn('reply_child_id');
|
||||||
$table->dropColumn('in_reply_to_id');
|
$table->dropColumn('in_reply_to_id');
|
||||||
|
|
|
@ -17,7 +17,7 @@ services:
|
||||||
#
|
#
|
||||||
# See: https://github.com/nginx-proxy/nginx-proxy/tree/main/docs
|
# See: https://github.com/nginx-proxy/nginx-proxy/tree/main/docs
|
||||||
proxy:
|
proxy:
|
||||||
image: nginxproxy/nginx-proxy:1.4
|
image: "nginxproxy/nginx-proxy:${DOCKER_PROXY_VERSION}"
|
||||||
container_name: "${DOCKER_ALL_CONTAINER_NAME_PREFIX}-proxy"
|
container_name: "${DOCKER_ALL_CONTAINER_NAME_PREFIX}-proxy"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
profiles:
|
profiles:
|
||||||
|
|
68
funding.json
Normal file
68
funding.json
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
{
|
||||||
|
"version": "v1.0.0",
|
||||||
|
"entity": {
|
||||||
|
"type": "individual",
|
||||||
|
"role": "owner",
|
||||||
|
"name": "Daniel Supernault",
|
||||||
|
"email": "danielsupernault@gmail.com",
|
||||||
|
"phone": "",
|
||||||
|
"description": "I'm the developer behind Pixelfed, an open-source, federated photo-sharing platform that prioritizes privacy, community, and creativity. With a passion for building ethical alternatives to mainstream social networks, I have championed decentralized technologies and empowered users to share their stories without compromising their digital autonomy. As the driving force behind Pixelfed, I combine innovative development with a thoughtful approach to user experience, fostering an online space that feels personal, authentic, and inclusive.",
|
||||||
|
"webpageUrl": {
|
||||||
|
"url": "https://github.com/pixelfed/pixelfed"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"projects": [
|
||||||
|
{
|
||||||
|
"guid": "pixelfed",
|
||||||
|
"name": "Pixelfed",
|
||||||
|
"description": "Pixelfed is a free, open-source photo-sharing platform designed to put users in control of their content. Built on the principles of decentralization, Pixelfed offers a refreshing alternative to traditional social media, prioritizing privacy, community, and ethical design. Whether you're an artist, photographer, or someone who loves sharing moments, Pixelfed lets you connect and create without intrusive ads, algorithms, or data exploitation. Fully federated and part of the Fediverse, Pixelfed empowers users to join or host their own instances while still connecting with a global network of creatives and communities. It's not just a platform—it's a movement toward a better, more user-centered internet.",
|
||||||
|
"webpageUrl": {
|
||||||
|
"url": "https://github.com/pixelfed/pixelfed"
|
||||||
|
},
|
||||||
|
"repositoryUrl": {
|
||||||
|
"url": "https://github.com/pixelfed/pixelfed"
|
||||||
|
},
|
||||||
|
"licenses": ["spdx:AGPL-3.0"],
|
||||||
|
"tags": ["activitypub", "fediverse", "laravel", "pixelfed"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"funding": {
|
||||||
|
"channels": [
|
||||||
|
{
|
||||||
|
"guid": "github-sponsors",
|
||||||
|
"type": "payment-provider",
|
||||||
|
"address": "https://github.com/sponsors/dansup",
|
||||||
|
"description": "Sponsor me through Github."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"guid": "paypal-sponsors",
|
||||||
|
"type": "payment-provider",
|
||||||
|
"address": "https://www.paypal.com/paypalme/dansup",
|
||||||
|
"description": "Sponsor me through Paypal."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"plans": [
|
||||||
|
{
|
||||||
|
"guid": "developer-time",
|
||||||
|
"status": "active",
|
||||||
|
"name": "Developer compensation",
|
||||||
|
"description": "This will cover the cost of one developer working part-time on the projects.",
|
||||||
|
"amount": 0,
|
||||||
|
"currency": "USD",
|
||||||
|
"frequency": "monthly",
|
||||||
|
"channels": ["github-sponsors", "paypal-sponsors"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"guid": "support-plan",
|
||||||
|
"status": "active",
|
||||||
|
"name": "Support plan",
|
||||||
|
"description": "Pay anything you wish/can to show your support for the projects.",
|
||||||
|
"amount": 0,
|
||||||
|
"currency": "USD",
|
||||||
|
"frequency": "one-time",
|
||||||
|
"channels": ["github-sponsors", "paypal-sponsors"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"history": []
|
||||||
|
}
|
||||||
|
}
|
842
package-lock.json
generated
842
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -71,6 +71,7 @@
|
||||||
"vue-loading-overlay": "^3.3.3",
|
"vue-loading-overlay": "^3.3.3",
|
||||||
"vue-timeago": "^5.1.2",
|
"vue-timeago": "^5.1.2",
|
||||||
"vue-tribute": "^1.0.7",
|
"vue-tribute": "^1.0.7",
|
||||||
|
"webgl-media-editor": "^0.0.1",
|
||||||
"zuck.js": "^1.6.0"
|
"zuck.js": "^1.6.0"
|
||||||
},
|
},
|
||||||
"collective": {
|
"collective": {
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue