Merge branch 'staging' into dev

pull/2928/head
daniel 2021-10-19 20:41:49 -06:00 zatwierdzone przez GitHub
commit f79486c448
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
55 zmienionych plików z 1253 dodań i 849 usunięć

Wyświetl plik

@ -2,6 +2,22 @@
## [Unreleased](https://github.com/pixelfed/pixelfed/compare/v0.11.1...dev)
### Updated
- Updated NotificationService, fix 500 bug. ([4a609dc3](https://github.com/pixelfed/pixelfed/commit/4a609dc3))
- Updated HttpSignatures, update instance actor headers. Fixes #2935. ([a900de21](https://github.com/pixelfed/pixelfed/commit/a900de21))
- Updated NoteTransformer, fix tag array. ([7b3e672d](https://github.com/pixelfed/pixelfed/commit/7b3e672d))
- Updated video presenters, add playsinline attribute to video tags. ([0299aa5b](https://github.com/pixelfed/pixelfed/commit/0299aa5b))
- Updated RemotePost, RemoteProfile components, add fallback avatars. ([754151dc](https://github.com/pixelfed/pixelfed/commit/754151dc))
- Updated FederationController, move well-known to api middleware and cache webfinger lookups. ([4505d1f0](https://github.com/pixelfed/pixelfed/commit/4505d1f0))
- Updated InstanceActorController, improve json seralization by not escaping slashes. ([0a8eb81b](https://github.com/pixelfed/pixelfed/commit/0a8eb81b))
- Refactor following & relationship logic. Replace FollowerObserver with FollowerService and added RelationshipService to cache results. Removed NotificationTransformer includes and replaced with cached services to improve performance and reduce database queries. ([80d9b939](https://github.com/pixelfed/pixelfed/commit/80d9b939))
- Updated PublicApiController, use AccountService in accountStatuses method. ([bef959f4](https://github.com/pixelfed/pixelfed/commit/bef959f4))
- Updated auth config, add throttle limit to password resets. ([2609c86a](https://github.com/pixelfed/pixelfed/commit/2609c86a))
- Updated StatusCard component, add relationship state button. ([0436b124](https://github.com/pixelfed/pixelfed/commit/0436b124))
- Updated Timeline component, cascade relationship state change. ([f4bd5672](https://github.com/pixelfed/pixelfed/commit/f4bd5672))
- Updated Activity component, only show context button for actionable activities. ([7886fd59](https://github.com/pixelfed/pixelfed/commit/7886fd59))
- ([](https://github.com/pixelfed/pixelfed/commit/))
## [v0.11.1 (2021-09-07)](https://github.com/pixelfed/pixelfed/compare/v0.11.0...v0.11.1)
### Added
- WebP Support ([069a0e4a](https://github.com/pixelfed/pixelfed/commit/069a0e4a))
@ -112,7 +128,6 @@
- Updated DirectMessageController, fix autocomplete bug. ([0f00be4d](https://github.com/pixelfed/pixelfed/commit/0f00be4d))
- Updated StoryService, fix division by zero bug. ([6ae1ba0a](https://github.com/pixelfed/pixelfed/commit/6ae1ba0a))
- Updated ApiV1Controller, fix empty public timeline bug. ([0584f9ee](https://github.com/pixelfed/pixelfed/commit/0584f9ee))
- ([](https://github.com/pixelfed/pixelfed/commit/))
## [v0.11.0 (2021-06-01)](https://github.com/pixelfed/pixelfed/compare/v0.10.10...v0.11.0)
### Added

Wyświetl plik

@ -55,6 +55,7 @@ use App\Services\{
MediaPathService,
PublicTimelineService,
ProfileService,
RelationshipService,
SearchApiV2Service,
StatusService,
MediaBlocklistService
@ -551,7 +552,7 @@ class ApiV1Controller extends Controller
*
* @param array|integer $id
*
* @return \App\Transformer\Api\RelationshipTransformer
* @return \App\Services\RelationshipService
*/
public function accountRelationshipsById(Request $request)
{
@ -563,12 +564,9 @@ class ApiV1Controller extends Controller
]);
$pid = $request->user()->profile_id ?? $request->user()->profile->id;
$ids = collect($request->input('id'));
$filtered = $ids->filter(function($v) use($pid) {
return $v != $pid;
$res = $ids->map(function($id) use($pid) {
return RelationshipService::get($pid, $id);
});
$relations = Profile::whereNull('status')->findOrFail($filtered->values());
$fractal = new Fractal\Resource\Collection($relations, new RelationshipTransformer());
$res = $this->fractal->createData($fractal)->toArray();
return response()->json($res);
}

Wyświetl plik

@ -35,14 +35,14 @@ class FederationController extends Controller
public function nodeinfoWellKnown()
{
abort_if(!config('federation.nodeinfo.enabled'), 404);
return response()->json(Nodeinfo::wellKnown())
return response()->json(Nodeinfo::wellKnown(), 200, [], JSON_UNESCAPED_SLASHES)
->header('Access-Control-Allow-Origin','*');
}
public function nodeinfo()
{
abort_if(!config('federation.nodeinfo.enabled'), 404);
return response()->json(Nodeinfo::get())
return response()->json(Nodeinfo::get(), 200, [], JSON_UNESCAPED_SLASHES)
->header('Access-Control-Allow-Origin','*');
}
@ -53,6 +53,11 @@ class FederationController extends Controller
abort_if(!$request->filled('resource'), 400);
$resource = $request->input('resource');
$hash = hash('sha256', $resource);
$key = 'federation:webfinger:sha256:' . $hash;
if($cached = Cache::get($key)) {
return response()->json($cached, 200, [], JSON_UNESCAPED_SLASHES);
}
$parsed = Nickname::normalizeProfileUrl($resource);
if(empty($parsed) || $parsed['domain'] !== config('pixelfed.domain.app')) {
abort(404);
@ -63,8 +68,9 @@ class FederationController extends Controller
return ProfileController::accountCheck($profile);
}
$webfinger = (new Webfinger($profile))->generate();
Cache::put($key, $webfinger, 43200);
return response()->json($webfinger, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES)
return response()->json($webfinger, 200, [], JSON_UNESCAPED_SLASHES)
->header('Access-Control-Allow-Origin','*');
}

Wyświetl plik

@ -12,6 +12,7 @@ use Auth, Cache;
use Illuminate\Http\Request;
use App\Jobs\FollowPipeline\FollowPipeline;
use App\Util\ActivityPub\Helpers;
use App\Services\FollowerService;
class FollowerController extends Controller
{
@ -70,7 +71,9 @@ class FollowerController extends Controller
]);
if($remote == true && config('federation.activitypub.remoteFollow') == true) {
$this->sendFollow($user, $target);
}
}
FollowerService::add($user->id, $target->id);
} elseif ($private == false && $isFollowing == 0) {
if($user->following()->count() >= Follower::MAX_FOLLOWING) {
abort(400, 'You cannot follow more than ' . Follower::MAX_FOLLOWING . ' accounts');
@ -87,6 +90,7 @@ class FollowerController extends Controller
if($remote == true && config('federation.activitypub.remoteFollow') == true) {
$this->sendFollow($user, $target);
}
FollowerService::add($user->id, $target->id);
FollowPipeline::dispatch($follower);
} else {
if($force == true) {
@ -101,6 +105,7 @@ class FollowerController extends Controller
Follower::whereProfileId($user->id)
->whereFollowingId($target->id)
->delete();
FollowerService::remove($user->id, $target->id);
}
}

Wyświetl plik

@ -15,10 +15,13 @@ use App\Jobs\ImportPipeline\ImportInstagram;
trait Instagram
{
public function instagram()
{
return view('settings.import.instagram.home');
}
public function instagram()
{
if(config_cache('pixelfed.import.instagram.enabled') != true) {
abort(404, 'Feature not enabled');
}
return view('settings.import.instagram.home');
}
public function instagramStart(Request $request)
{

Wyświetl plik

@ -6,8 +6,11 @@ use Illuminate\Http\Request;
trait Mastodon
{
public function mastodon()
{
return view('settings.import.mastodon.home');
}
public function mastodon()
{
if(config_cache('pixelfed.import.instagram.enabled') != true) {
abort(404, 'Feature not enabled');
}
return view('settings.import.mastodon.home');
}
}

Wyświetl plik

@ -11,10 +11,6 @@ class ImportController extends Controller
public function __construct()
{
$this->middleware('auth');
if(config_cache('pixelfed.import.instagram.enabled') != true) {
abort(404, 'Feature not enabled');
}
}
}

Wyświetl plik

@ -12,7 +12,7 @@ class InstanceActorController extends Controller
{
$res = Cache::rememberForever(InstanceActor::PROFILE_KEY, function() {
$res = (new InstanceActor())->first()->getActor();
return json_encode($res);
return json_encode($res, JSON_UNESCAPED_SLASHES);
});
return response($res)->header('Content-Type', 'application/json');
}

Wyświetl plik

@ -11,14 +11,10 @@ use App\Services\FollowerService;
class PollController extends Controller
{
public function __construct()
{
abort_if(!config_cache('instance.polls.enabled'), 404);
}
public function getPoll(Request $request, $id)
{
abort_if(!config_cache('instance.polls.enabled'), 404);
$poll = Poll::findOrFail($id);
$status = Status::findOrFail($poll->status_id);
if($status->scope != 'public') {
@ -34,6 +30,8 @@ class PollController extends Controller
public function vote(Request $request, $id)
{
abort_if(!config_cache('instance.polls.enabled'), 404);
abort_unless($request->user(), 403);
$this->validate($request, [

Wyświetl plik

@ -51,11 +51,11 @@ class PublicApiController extends Controller
protected function getUserData($user)
{
if(!$user) {
return [];
} else {
if(!$user) {
return [];
} else {
return AccountService::get($user->profile_id);
}
}
}
protected function getLikes($status)
@ -94,12 +94,12 @@ class PublicApiController extends Controller
$status = Status::whereProfileId($profile->id)->findOrFail($postid);
$this->scopeCheck($profile, $status);
if(!$request->user()) {
$res = ['status' => StatusService::get($status->id)];
$res = ['status' => StatusService::get($status->id)];
} else {
$item = new Fractal\Resource\Item($status, new StatusStatelessTransformer());
$res = [
'status' => $this->fractal->createData($item)->toArray(),
];
$item = new Fractal\Resource\Item($status, new StatusStatelessTransformer());
$res = [
'status' => $this->fractal->createData($item)->toArray(),
];
}
return response()->json($res);
@ -200,14 +200,14 @@ class PublicApiController extends Controller
public function statusLikes(Request $request, $username, $id)
{
abort_if(!$request->user(), 404);
abort_if(!$request->user(), 404);
$status = Status::findOrFail($id);
$this->scopeCheck($status->profile, $status);
$page = $request->input('page');
if($page && $page >= 3 && $request->user()->profile_id != $status->profile_id) {
return response()->json([
'data' => []
]);
return response()->json([
'data' => []
]);
}
$likes = $this->getLikes($status);
return response()->json([
@ -217,15 +217,15 @@ class PublicApiController extends Controller
public function statusShares(Request $request, $username, $id)
{
abort_if(!$request->user(), 404);
abort_if(!$request->user(), 404);
$profile = Profile::whereUsername($username)->whereNull('status')->firstOrFail();
$status = Status::whereProfileId($profile->id)->findOrFail($id);
$this->scopeCheck($profile, $status);
$page = $request->input('page');
if($page && $page >= 3 && $request->user()->profile_id != $status->profile_id) {
return response()->json([
'data' => []
]);
return response()->json([
'data' => []
]);
}
$shares = $this->getShares($status);
return response()->json([
@ -300,7 +300,7 @@ class PublicApiController extends Controller
'scope',
'local'
)
->where('id', $dir, $id)
->where('id', $dir, $id)
->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
->whereNotIn('profile_id', $filtered)
->whereLocal(true)
@ -309,7 +309,7 @@ class PublicApiController extends Controller
->limit($limit)
->get()
->map(function($s) use ($user) {
$status = StatusService::get($s->id);
$status = StatusService::getFull($s->id, $user->profile_id);
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $s->id);
return $status;
});
@ -335,16 +335,21 @@ class PublicApiController extends Controller
'reblogs_count',
'updated_at'
)
->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
->whereNotIn('profile_id', $filtered)
->with('profile', 'hashtags', 'mentions')
->whereLocal(true)
->whereScope('public')
->orderBy('id', 'desc')
->simplePaginate($limit);
->limit($limit)
->get()
->map(function($s) use ($user) {
$status = StatusService::getFull($s->id, $user->profile_id);
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $s->id);
return $status;
});
$fractal = new Fractal\Resource\Collection($timeline, new StatusTransformer());
$res = $this->fractal->createData($fractal)->toArray();
$res = $timeline->toArray();
}
return response()->json($res);
@ -389,12 +394,12 @@ class PublicApiController extends Controller
});
if($recentFeed == true) {
$key = 'profile:home-timeline-cursor:'.$user->id;
$ttl = now()->addMinutes(30);
$min = Cache::remember($key, $ttl, function() use($pid) {
$res = StatusView::whereProfileId($pid)->orderByDesc('status_id')->first();
return $res ? $res->status_id : null;
});
$key = 'profile:home-timeline-cursor:'.$user->id;
$ttl = now()->addMinutes(30);
$min = Cache::remember($key, $ttl, function() use($pid) {
$res = StatusView::whereProfileId($pid)->orderByDesc('status_id')->first();
return $res ? $res->status_id : null;
});
}
$filtered = $user ? UserFilterService::filters($user->profile_id) : [];
@ -403,16 +408,16 @@ class PublicApiController extends Controller
$textOnlyReplies = false;
if(config('exp.top')) {
$textOnlyPosts = (bool) Redis::zscore('pf:tl:top', $pid);
$textOnlyReplies = (bool) Redis::zscore('pf:tl:replies', $pid);
$textOnlyPosts = (bool) Redis::zscore('pf:tl:top', $pid);
$textOnlyReplies = (bool) Redis::zscore('pf:tl:replies', $pid);
if($textOnlyPosts) {
array_push($types, 'text');
}
if($textOnlyPosts) {
array_push($types, 'text');
}
}
if(config('exp.polls') == true) {
array_push($types, 'poll');
array_push($types, 'poll');
}
if($min || $max) {
@ -438,10 +443,10 @@ class PublicApiController extends Controller
'created_at',
'updated_at'
)
->whereIn('type', $types)
->whereIn('type', $types)
->when($textOnlyReplies != true, function($q, $textOnlyReplies) {
return $q->whereNull('in_reply_to_id');
})
return $q->whereNull('in_reply_to_id');
})
->with('profile', 'hashtags', 'mentions')
->where('id', $dir, $id)
->whereIn('profile_id', $following)
@ -471,10 +476,10 @@ class PublicApiController extends Controller
'created_at',
'updated_at'
)
->whereIn('type', $types)
->when(!$textOnlyReplies, function($q, $textOnlyReplies) {
return $q->whereNull('in_reply_to_id');
})
->whereIn('type', $types)
->when(!$textOnlyReplies, function($q, $textOnlyReplies) {
return $q->whereNull('in_reply_to_id');
})
->with('profile', 'hashtags', 'mentions')
->whereIn('profile_id', $following)
->whereNotIn('profile_id', $filtered)
@ -527,7 +532,7 @@ class PublicApiController extends Controller
'scope',
'created_at',
)
->where('id', $dir, $id)
->where('id', $dir, $id)
->whereNotIn('profile_id', $filtered)
->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
->whereNotNull('uri')
@ -543,19 +548,19 @@ class PublicApiController extends Controller
});
$res = $timeline->toArray();
} else {
$timeline = Status::select(
'id',
'uri',
'type',
'scope',
'created_at',
)
->whereNotIn('profile_id', $filtered)
->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
->whereNotNull('uri')
->whereScope('public')
->where('id', '>', $amin)
->orderBy('created_at', 'desc')
$timeline = Status::select(
'id',
'uri',
'type',
'scope',
'created_at',
)
->whereNotIn('profile_id', $filtered)
->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
->whereNotNull('uri')
->whereScope('public')
->where('id', '>', $amin)
->orderBy('created_at', 'desc')
->limit($limit)
->get()
->map(function($s) use ($user) {
@ -563,7 +568,7 @@ class PublicApiController extends Controller
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $s->id);
return $status;
});
$res = $timeline->toArray();
$res = $timeline->toArray();
}
return response()->json($res);
@ -605,10 +610,10 @@ class PublicApiController extends Controller
return response()->json([]);
}
if(!$profile->domain && !$profile->user->settings->show_profile_followers) {
return response()->json([]);
return response()->json([]);
}
if(!$owner && $request->page > 5) {
return [];
return [];
}
$res = Follower::select('id', 'profile_id', 'following_id')
@ -639,11 +644,11 @@ class PublicApiController extends Controller
abort_if($owner == false && $profile->is_private == true && !$profile->followedBy(Auth::user()->profile), 404);
if(!$profile->domain) {
abort_if($profile->user->settings->show_profile_following == false && $owner == false, 404);
abort_if($profile->user->settings->show_profile_following == false && $owner == false, 404);
}
if(!$owner && $request->page > 5) {
return [];
return [];
}
if($search) {
@ -676,14 +681,15 @@ class PublicApiController extends Controller
]);
$user = $request->user();
$profile = Profile::whereNull('status')->findOrFail($id);
$profile = AccountService::get($id);
abort_if(!$profile, 404);
$limit = $request->limit ?? 9;
$max_id = $request->max_id;
$min_id = $request->min_id;
$scope = ['photo', 'photo:album', 'video', 'video:album'];
if($profile->is_private) {
if($profile['locked']) {
if(!$user) {
return response()->json([]);
}
@ -700,7 +706,7 @@ class PublicApiController extends Controller
$following = Follower::whereProfileId($pid)->pluck('following_id');
return $following->push($pid)->toArray();
});
$visibility = true == in_array($profile->id, $following) ? ['public', 'unlisted', 'private'] : ['public', 'unlisted'];
$visibility = true == in_array($profile['id'], $following) ? ['public', 'unlisted', 'private'] : ['public', 'unlisted'];
} else {
$visibility = ['public', 'unlisted'];
}
@ -708,15 +714,7 @@ class PublicApiController extends Controller
$dir = $min_id ? '>' : '<';
$id = $min_id ?? $max_id;
$res = Status::select(
'id',
'profile_id',
'type',
'scope',
'local',
'created_at'
)
->whereProfileId($profile->id)
$res = Status::whereProfileId($profile['id'])
->whereNull('in_reply_to_id')
->whereNull('reblog_of_id')
->whereIn('type', $scope)
@ -726,18 +724,18 @@ class PublicApiController extends Controller
->orderByDesc('id')
->get()
->map(function($s) use($user) {
try {
$status = StatusService::get($s->id, false);
} catch (\Exception $e) {
$status = false;
}
try {
$status = StatusService::get($s->id, false);
} catch (\Exception $e) {
$status = false;
}
if($user && $status) {
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $s->id);
$status['favourited'] = (bool) LikeService::liked($user->profile_id, $s->id);
}
return $status;
})
->filter(function($s) {
return $s;
return $s;
})
->values();

Wyświetl plik

@ -11,7 +11,7 @@ class InstanceActor extends Model
const PROFILE_BASE = '/i/actor';
const KEY_ID = '/i/actor#main-key';
const PROFILE_KEY = 'federation:_v2:instance:actor:profile';
const PROFILE_KEY = 'federation:_v3:instance:actor:profile';
const PKI_PUBLIC = 'federation:_v1:instance:actor:profile:pki_public';
const PKI_PRIVATE = 'federation:_v1:instance:actor:profile:pki_private';

Wyświetl plik

@ -1,64 +0,0 @@
<?php
namespace App\Observers;
use App\Follower;
use App\Services\FollowerService;
class FollowerObserver
{
/**
* Handle the Follower "created" event.
*
* @param \App\Models\Follower $follower
* @return void
*/
public function created(Follower $follower)
{
FollowerService::add($follower->profile_id, $follower->following_id);
}
/**
* Handle the Follower "updated" event.
*
* @param \App\Models\Follower $follower
* @return void
*/
public function updated(Follower $follower)
{
FollowerService::add($follower->profile_id, $follower->following_id);
}
/**
* Handle the Follower "deleted" event.
*
* @param \App\Models\Follower $follower
* @return void
*/
public function deleted(Follower $follower)
{
FollowerService::remove($follower->profile_id, $follower->following_id);
}
/**
* Handle the Follower "restored" event.
*
* @param \App\Models\Follower $follower
* @return void
*/
public function restored(Follower $follower)
{
FollowerService::add($follower->profile_id, $follower->following_id);
}
/**
* Handle the Follower "force deleted" event.
*
* @param \App\Models\Follower $follower
* @return void
*/
public function forceDeleted(Follower $follower)
{
FollowerService::remove($follower->profile_id, $follower->following_id);
}
}

Wyświetl plik

@ -4,7 +4,6 @@ namespace App\Providers;
use App\Observers\{
AvatarObserver,
FollowerObserver,
LikeObserver,
NotificationObserver,
ModLogObserver,
@ -15,7 +14,6 @@ use App\Observers\{
};
use App\{
Avatar,
Follower,
Like,
Notification,
ModLog,
@ -50,7 +48,6 @@ class AppServiceProvider extends ServiceProvider
StatusHashtag::observe(StatusHashtagObserver::class);
User::observe(UserObserver::class);
UserFilter::observe(UserFilterObserver::class);
Follower::observe(FollowerObserver::class);
Horizon::auth(function ($request) {
return Auth::check() && $request->user()->is_admin;
});

Wyświetl plik

@ -2,7 +2,7 @@
namespace App\Services;
use Zttp\Zttp;
use Illuminate\Support\Facades\Http;
use App\Profile;
use App\Util\ActivityPub\Helpers;
use App\Util\ActivityPub\HttpSignature;
@ -15,14 +15,13 @@ class ActivityPubFetchService
return 0;
}
$headers = HttpSignature::instanceActorSign($url, false, [
'Accept' => 'application/activity+json, application/json',
'User-Agent' => '(Pixelfed/'.config('pixelfed.version').'; +'.config('app.url').')'
]);
$headers = HttpSignature::instanceActorSign($url, false);
$headers['Accept'] = 'application/activity+json, application/json';
$headers['User-Agent'] = '(Pixelfed/'.config('pixelfed.version').'; +'.config('app.url').')';
return Zttp::withHeaders($headers)
return Http::withHeaders($headers)
->timeout(30)
->get($url)
->body();
}
}
}

Wyświetl plik

@ -17,12 +17,14 @@ class FollowerService
public static function add($actor, $target)
{
RelationshipService::refresh($actor, $target);
Redis::zadd(self::FOLLOWING_KEY . $actor, $target, $target);
Redis::zadd(self::FOLLOWERS_KEY . $target, $actor, $actor);
}
public static function remove($actor, $target)
{
RelationshipService::refresh($actor, $target);
Redis::zrem(self::FOLLOWING_KEY . $actor, $target);
Redis::zrem(self::FOLLOWERS_KEY . $target, $actor);
Cache::forget('pf:services:follow:audience:' . $actor);

Wyświetl plik

@ -7,6 +7,13 @@ use App\Instance;
class InstanceService
{
public static function getByDomain($domain)
{
return Cache::remember('pf:services:instance:by_domain:'.$domain, 3600, function() use($domain) {
return Instance::whereDomain($domain)->first();
});
}
public static function getBannedDomains()
{
return Cache::remember('instances:banned:domains', now()->addHours(12), function() {

Wyświetl plik

@ -27,7 +27,10 @@ class NotificationService {
$ids = self::coldGet($id, $start, $stop);
}
foreach($ids as $id) {
$res->push(self::getNotification($id));
$n = self::getNotification($id);
if($n != null) {
$res->push($n);
}
}
return $res;
}
@ -56,7 +59,10 @@ class NotificationService {
$res = collect([]);
foreach($ids as $id) {
$res->push(self::getNotification($id));
$n = self::getNotification($id);
if($n != null) {
$res->push($n);
}
}
return $res->toArray();
}
@ -71,7 +77,10 @@ class NotificationService {
$res = collect([]);
foreach($ids as $id) {
$res->push(self::getNotification($id));
$n = self::getNotification($id);
if($n != null) {
$res->push($n);
}
}
return $res->toArray();
}
@ -129,7 +138,12 @@ class NotificationService {
public static function getNotification($id)
{
return Cache::remember('service:notification:'.$id, now()->addDays(3), function() use($id) {
$n = Notification::with('item')->findOrFail($id);
$n = Notification::with('item')->find($id);
if(!$n) {
return null;
}
$fractal = new Fractal\Manager();
$fractal->setSerializer(new ArraySerializer());
$resource = new Fractal\Resource\Item($n, new NotificationTransformer());

Wyświetl plik

@ -0,0 +1,86 @@
<?php
namespace App\Services;
use Illuminate\Support\Facades\Cache;
use App\Follower;
use App\FollowRequest;
use App\Profile;
use App\UserFilter;
class RelationshipService
{
const CACHE_KEY = 'pf:services:urel:';
public static function get($aid, $tid)
{
$actor = AccountService::get($aid);
$target = AccountService::get($tid);
if(!$actor || !$target) {
return self::defaultRelation($tid);
}
if($actor['id'] === $target['id']) {
return self::defaultRelation($tid);
}
return Cache::remember(self::key("a_{$aid}:t_{$tid}"), 1209600, function() use($aid, $tid) {
return [
'id' => (string) $tid,
'following' => Follower::whereProfileId($aid)->whereFollowingId($tid)->exists(),
'followed_by' => Follower::whereProfileId($tid)->whereFollowingId($aid)->exists(),
'blocking' => UserFilter::whereUserId($aid)
->whereFilterableType('App\Profile')
->whereFilterableId($tid)
->whereFilterType('block')
->exists(),
'muting' => UserFilter::whereUserId($aid)
->whereFilterableType('App\Profile')
->whereFilterableId($tid)
->whereFilterType('mute')
->exists(),
'muting_notifications' => null,
'requested' => FollowRequest::whereFollowerId($aid)
->whereFollowingId($tid)
->exists(),
'domain_blocking' => null,
'showing_reblogs' => null,
'endorsed' => false
];
});
}
public static function delete($aid, $tid)
{
return Cache::forget(self::key("a_{$aid}:t_{$tid}"));
}
public static function refresh($aid, $tid)
{
self::delete($tid, $aid);
self::delete($aid, $tid);
self::get($tid, $aid);
return self::get($aid, $tid);
}
public static function defaultRelation($tid)
{
return [
'id' => (string) $tid,
'following' => false,
'followed_by' => false,
'blocking' => false,
'muting' => false,
'muting_notifications' => null,
'requested' => false,
'domain_blocking' => null,
'showing_reblogs' => null,
'endorsed' => false
];
}
protected static function key($suffix)
{
return self::CACHE_KEY . $suffix;
}
}

Wyświetl plik

@ -40,6 +40,13 @@ class StatusService {
});
}
public static function getFull($id, $pid, $publicOnly = true)
{
$res = self::get($id, $publicOnly);
$res['relationship'] = RelationshipService::get($pid, $res['account']['id']);
return $res;
}
public static function del($id)
{
$status = self::get($id);

Wyświetl plik

@ -35,7 +35,7 @@ class Note extends Fractal\TransformerAbstract
'href' => $parent->permalink(),
'name' => $name
];
$mentions = array_merge($reply, $mentions);
array_push($mentions, $reply);
}
}

Wyświetl plik

@ -2,50 +2,51 @@
namespace App\Transformer\Api;
use App\{
Notification,
Status
};
use App\Notification;
use App\Services\AccountService;
use App\Services\HashidService;
use App\Services\RelationshipService;
use App\Services\StatusService;
use League\Fractal;
class NotificationTransformer extends Fractal\TransformerAbstract
{
protected $defaultIncludes = [
'account',
'status',
'relationship',
'modlog',
'tagged'
];
public function transform(Notification $notification)
{
return [
$res = [
'id' => (string) $notification->id,
'type' => $this->replaceTypeVerb($notification->action),
'created_at' => (string) $notification->created_at->format('c'),
];
}
public function includeAccount(Notification $notification)
{
return $this->item($notification->actor, new AccountTransformer());
}
$n = $notification;
public function includeStatus(Notification $notification)
{
$item = $notification;
if($item->item_id && $item->item_type == 'App\Status') {
$status = Status::with('media')->find($item->item_id);
if($status) {
return $this->item($status, new StatusTransformer());
} else {
return null;
}
} else {
return null;
if($n->actor_id) {
$res['account'] = AccountService::get($n->actor_id);
$res['relationship'] = RelationshipService::get($n->actor_id, $n->profile_id);
}
if($n->item_id && $n->item_type == 'App\Status') {
$res['status'] = StatusService::get($n->item_id, false);
}
if($n->item_id && $n->item_type == 'App\ModLog') {
$ml = $n->item;
$res['modlog'] = [
'id' => $ml->object_uid,
'url' => url('/i/admin/users/modlogs/' . $ml->object_uid)
];
}
if($n->item_id && $n->item_type == 'App\MediaTag') {
$ml = $n->item;
$res['tagged'] = [
'username' => $ml->tagged_username,
'post_url' => '/p/'.HashidService::encode($ml->status_id)
];
}
return $res;
}
public function replaceTypeVerb($verb)
@ -57,56 +58,21 @@ class NotificationTransformer extends Fractal\TransformerAbstract
'reblog' => 'share',
'share' => 'share',
'like' => 'favourite',
'group:like' => 'favourite',
'comment' => 'comment',
'admin.user.modlog.comment' => 'modlog',
'tagged' => 'tagged',
'group:comment' => 'group:comment',
'story:react' => 'story:react',
'story:comment' => 'story:comment'
'story:comment' => 'story:comment',
'group:join:approved' => 'group:join:approved',
'group:join:rejected' => 'group:join:rejected'
];
if(!isset($verbs[$verb])) {
return $verb;
}
return $verbs[$verb];
}
public function includeRelationship(Notification $notification)
{
return $this->item($notification->actor, new RelationshipTransformer());
}
public function includeModlog(Notification $notification)
{
$n = $notification;
if($n->item_id && $n->item_type == 'App\ModLog') {
$ml = $n->item;
if(!empty($ml)) {
$res = $this->item($ml, function($ml) {
return [
'id' => $ml->object_uid,
'url' => url('/i/admin/users/modlogs/' . $ml->object_uid)
];
});
return $res;
} else {
return null;
}
} else {
return null;
}
}
public function includeTagged(Notification $notification)
{
$n = $notification;
if($n->item_id && $n->item_type == 'App\MediaTag') {
$ml = $n->item;
$res = $this->item($ml, function($ml) {
return [
'username' => $ml->tagged_username,
'post_url' => '/p/'.HashidService::encode($ml->status_id)
];
});
return $res;
} else {
return null;
}
}
}

Wyświetl plik

@ -43,7 +43,7 @@ class HttpSignature {
$digest = self::_digest($body);
}
$headers = self::_headersToSign($url, $body ? $digest : false);
$headers = array_unique(array_merge($headers, $addlHeaders));
$headers = array_merge($headers, $addlHeaders);
$stringToSign = self::_headersToSigningString($headers);
$signedHeaders = implode(' ', array_map('strtolower', array_keys($headers)));
$key = openssl_pkey_get_private($privateKey);
@ -133,7 +133,6 @@ class HttpSignature {
'Date' => $date->format('D, d M Y H:i:s \G\M\T'),
'Host' => parse_url($url, PHP_URL_HOST),
'Accept' => 'application/activity+json, application/json',
'Content-Type' => 'application/activity+json'
];
if($digest) {

Wyświetl plik

@ -455,6 +455,7 @@ class Inbox
Cache::forget('profile:follower_count:'.$actor->id);
Cache::forget('profile:following_count:'.$target->id);
Cache::forget('profile:following_count:'.$actor->id);
FollowerService::add($actor->id, $target->id);
} else {
$follower = new Follower;
@ -464,6 +465,7 @@ class Inbox
$follower->save();
FollowPipeline::dispatch($follower);
FollowerService::add($actor->id, $target->id);
// send Accept to remote profile
$accept = [
@ -722,6 +724,7 @@ class Inbox
->whereItemId($following->id)
->whereItemType('App\Profile')
->forceDelete();
FollowerService::remove($profile->id, $following->id);
break;
case 'Like':

Wyświetl plik

@ -10,34 +10,29 @@ class Nodeinfo {
public static function get()
{
$res = Cache::remember('api:nodeinfo', now()->addMinutes(15), function () {
$activeHalfYear = Cache::remember('api:nodeinfo:ahy', now()->addHours(12), function() {
// todo: replace with last_active_at after July 9, 2021 (96afc3e781)
$count = collect([]);
$likes = Like::select('profile_id')->with('actor')->where('created_at', '>', now()->subMonths(6)->toDateTimeString())->groupBy('profile_id')->get()->filter(function($like) {return $like->actor && $like->actor->domain == null;})->pluck('profile_id')->toArray();
$count = $count->merge($likes);
$statuses = Status::select('profile_id')->whereLocal(true)->where('created_at', '>', now()->subMonths(6)->toDateTimeString())->groupBy('profile_id')->pluck('profile_id')->toArray();
$count = $count->merge($statuses);
$profiles = User::select('profile_id', 'last_active_at')
->whereNotNull('last_active_at')
$res = Cache::remember('api:nodeinfo', 300, function () {
$activeHalfYear = Cache::remember('api:nodeinfo:ahy', 172800, function() {
return User::select('last_active_at')
->where('last_active_at', '>', now()->subMonths(6))
->pluck('profile_id')
->toArray();
$newProfiles = User::select('profile_id', 'last_active_at', 'created_at')
->whereNull('last_active_at')
->where('created_at', '>', now()->subMonths(6))
->pluck('profile_id')
->toArray();
$count = $count->merge($newProfiles);
$count = $count->merge($profiles);
return $count->unique()->count();
->orWhere('created_at', '>', now()->subMonths(6))
->count();
});
$activeMonth = Cache::remember('api:nodeinfo:am', now()->addHours(2), function() {
$activeMonth = Cache::remember('api:nodeinfo:am', 172800, function() {
return User::select('last_active_at')
->where('last_active_at', '>', now()->subMonths(1))
->orWhere('created_at', '>', now()->subMonths(1))
->count();
});
$users = Cache::remember('api:nodeinfo:users', 43200, function() {
return User::count();
});
$statuses = Cache::remember('api:nodeinfo:statuses', 21600, function() {
return Status::whereLocal(true)->count();
});
return [
'metadata' => [
'nodeName' => config_cache('app.name'),
@ -59,10 +54,10 @@ class Nodeinfo {
'version' => config('pixelfed.version'),
],
'usage' => [
'localPosts' => Status::whereLocal(true)->count(),
'localPosts' => $statuses,
'localComments' => 0,
'users' => [
'total' => User::count(),
'total' => $users,
'activeHalfyear' => (int) $activeHalfYear,
'activeMonth' => (int) $activeMonth,
],

1261
composer.lock wygenerowano

Plik diff jest za duży Load Diff

Wyświetl plik

@ -96,6 +96,7 @@ return [
'provider' => 'users',
'table' => 'password_resets',
'expire' => 60,
'throttle' => 60,
],
],

File diff suppressed because one or more lines are too long

2
public/js/admin.js vendored

File diff suppressed because one or more lines are too long

2
public/js/app.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
public/js/direct.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
public/js/rempos.js vendored

File diff suppressed because one or more lines are too long

2
public/js/rempro.js vendored

File diff suppressed because one or more lines are too long

2
public/js/status.js vendored

File diff suppressed because one or more lines are too long

Wyświetl plik

@ -1 +1 @@
(window.webpackJsonp=window.webpackJsonp||[]).push([[27],{15:function(e,a,o){e.exports=o("YMO/")},"YMO/":function(e,a,o){(function(e){function o(e){return(o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}ace.define("ace/theme/monokai",["require","exports","module","ace/lib/dom"],(function(e,a,o){a.isDark=!0,a.cssClass="ace-monokai",a.cssText=".ace-monokai .ace_gutter {background: #2F3129;color: #8F908A}.ace-monokai .ace_print-margin {width: 1px;background: #555651}.ace-monokai {background-color: #272822;color: #F8F8F2}.ace-monokai .ace_cursor {color: #F8F8F0}.ace-monokai .ace_marker-layer .ace_selection {background: #49483E}.ace-monokai.ace_multiselect .ace_selection.ace_start {box-shadow: 0 0 3px 0px #272822;}.ace-monokai .ace_marker-layer .ace_step {background: rgb(102, 82, 0)}.ace-monokai .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid #49483E}.ace-monokai .ace_marker-layer .ace_active-line {background: #202020}.ace-monokai .ace_gutter-active-line {background-color: #272727}.ace-monokai .ace_marker-layer .ace_selected-word {border: 1px solid #49483E}.ace-monokai .ace_invisible {color: #52524d}.ace-monokai .ace_entity.ace_name.ace_tag,.ace-monokai .ace_keyword,.ace-monokai .ace_meta.ace_tag,.ace-monokai .ace_storage {color: #F92672}.ace-monokai .ace_punctuation,.ace-monokai .ace_punctuation.ace_tag {color: #fff}.ace-monokai .ace_constant.ace_character,.ace-monokai .ace_constant.ace_language,.ace-monokai .ace_constant.ace_numeric,.ace-monokai .ace_constant.ace_other {color: #AE81FF}.ace-monokai .ace_invalid {color: #F8F8F0;background-color: #F92672}.ace-monokai .ace_invalid.ace_deprecated {color: #F8F8F0;background-color: #AE81FF}.ace-monokai .ace_support.ace_constant,.ace-monokai .ace_support.ace_function {color: #66D9EF}.ace-monokai .ace_fold {background-color: #A6E22E;border-color: #F8F8F2}.ace-monokai .ace_storage.ace_type,.ace-monokai .ace_support.ace_class,.ace-monokai .ace_support.ace_type {font-style: italic;color: #66D9EF}.ace-monokai .ace_entity.ace_name.ace_function,.ace-monokai .ace_entity.ace_other,.ace-monokai .ace_entity.ace_other.ace_attribute-name,.ace-monokai .ace_variable {color: #A6E22E}.ace-monokai .ace_variable.ace_parameter {font-style: italic;color: #FD971F}.ace-monokai .ace_string {color: #E6DB74}.ace-monokai .ace_comment {color: #75715E}.ace-monokai .ace_indent-guide {background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWPQ0FD0ZXBzd/wPAAjVAoxeSgNeAAAAAElFTkSuQmCC) right repeat-y}",e("../lib/dom").importCssString(a.cssText,a.cssClass)})),ace.require(["ace/theme/monokai"],(function(c){"object"==o(e)&&"object"==o(a)&&e&&(e.exports=c)}))}).call(this,o("YuTi")(e))},YuTi:function(e,a){e.exports=function(e){return e.webpackPolyfill||(e.deprecate=function(){},e.paths=[],e.children||(e.children=[]),Object.defineProperty(e,"loaded",{enumerable:!0,get:function(){return e.l}}),Object.defineProperty(e,"id",{enumerable:!0,get:function(){return e.i}}),e.webpackPolyfill=1),e}}},[[15,0]]]);
(window.webpackJsonp=window.webpackJsonp||[]).push([[25],{15:function(e,a,o){e.exports=o("YMO/")},"YMO/":function(e,a,o){(function(e){function o(e){return(o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}ace.define("ace/theme/monokai",["require","exports","module","ace/lib/dom"],(function(e,a,o){a.isDark=!0,a.cssClass="ace-monokai",a.cssText=".ace-monokai .ace_gutter {background: #2F3129;color: #8F908A}.ace-monokai .ace_print-margin {width: 1px;background: #555651}.ace-monokai {background-color: #272822;color: #F8F8F2}.ace-monokai .ace_cursor {color: #F8F8F0}.ace-monokai .ace_marker-layer .ace_selection {background: #49483E}.ace-monokai.ace_multiselect .ace_selection.ace_start {box-shadow: 0 0 3px 0px #272822;}.ace-monokai .ace_marker-layer .ace_step {background: rgb(102, 82, 0)}.ace-monokai .ace_marker-layer .ace_bracket {margin: -1px 0 0 -1px;border: 1px solid #49483E}.ace-monokai .ace_marker-layer .ace_active-line {background: #202020}.ace-monokai .ace_gutter-active-line {background-color: #272727}.ace-monokai .ace_marker-layer .ace_selected-word {border: 1px solid #49483E}.ace-monokai .ace_invisible {color: #52524d}.ace-monokai .ace_entity.ace_name.ace_tag,.ace-monokai .ace_keyword,.ace-monokai .ace_meta.ace_tag,.ace-monokai .ace_storage {color: #F92672}.ace-monokai .ace_punctuation,.ace-monokai .ace_punctuation.ace_tag {color: #fff}.ace-monokai .ace_constant.ace_character,.ace-monokai .ace_constant.ace_language,.ace-monokai .ace_constant.ace_numeric,.ace-monokai .ace_constant.ace_other {color: #AE81FF}.ace-monokai .ace_invalid {color: #F8F8F0;background-color: #F92672}.ace-monokai .ace_invalid.ace_deprecated {color: #F8F8F0;background-color: #AE81FF}.ace-monokai .ace_support.ace_constant,.ace-monokai .ace_support.ace_function {color: #66D9EF}.ace-monokai .ace_fold {background-color: #A6E22E;border-color: #F8F8F2}.ace-monokai .ace_storage.ace_type,.ace-monokai .ace_support.ace_class,.ace-monokai .ace_support.ace_type {font-style: italic;color: #66D9EF}.ace-monokai .ace_entity.ace_name.ace_function,.ace-monokai .ace_entity.ace_other,.ace-monokai .ace_entity.ace_other.ace_attribute-name,.ace-monokai .ace_variable {color: #A6E22E}.ace-monokai .ace_variable.ace_parameter {font-style: italic;color: #FD971F}.ace-monokai .ace_string {color: #E6DB74}.ace-monokai .ace_comment {color: #75715E}.ace-monokai .ace_indent-guide {background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAEklEQVQImWPQ0FD0ZXBzd/wPAAjVAoxeSgNeAAAAAElFTkSuQmCC) right repeat-y}",e("../lib/dom").importCssString(a.cssText,a.cssClass)})),ace.require(["ace/theme/monokai"],(function(c){"object"==o(e)&&"object"==o(a)&&e&&(e.exports=c)}))}).call(this,o("YuTi")(e))},YuTi:function(e,a){e.exports=function(e){return e.webpackPolyfill||(e.deprecate=function(){},e.paths=[],e.children||(e.children=[]),Object.defineProperty(e,"loaded",{enumerable:!0,get:function(){return e.l}}),Object.defineProperty(e,"id",{enumerable:!0,get:function(){return e.i}}),e.webpackPolyfill=1),e}}},[[15,0]]]);

File diff suppressed because one or more lines are too long

2
public/js/vendor.js vendored

File diff suppressed because one or more lines are too long

Wyświetl plik

@ -1,10 +1,10 @@
{
"/js/manifest.js": "/js/manifest.js?id=7db827d654313dce4250",
"/js/vendor.js": "/js/vendor.js?id=c1d209d56a681363a028",
"/js/vendor.js": "/js/vendor.js?id=4736aa0fc51d85c921c6",
"/js/ace.js": "/js/ace.js?id=11e5550a450fece75c33",
"/js/activity.js": "/js/activity.js?id=c6dbcb16ae7e086e2695",
"/js/admin.js": "/js/admin.js?id=8d48022b71fa67e40d80",
"/js/app.js": "/js/app.js?id=f48d6aa0901c53c13ee0",
"/js/activity.js": "/js/activity.js?id=ccc3329aacb23a16d0d0",
"/js/admin.js": "/js/admin.js?id=bc1542fd4aa4e71daad7",
"/js/app.js": "/js/app.js?id=13bdbfb3f3f2c205c0ac",
"/css/app.css": "/css/app.css?id=de797b5388be16568353",
"/css/appdark.css": "/css/appdark.css?id=3da08a3e37579777460a",
"/css/admin.css": "/css/admin.css?id=ef822512ab2cfacef881",
@ -12,25 +12,25 @@
"/css/quill.css": "/css/quill.css?id=ece45380f676947dd7f8",
"/js/collectioncompose.js": "/js/collectioncompose.js?id=6678ddbea8c68875f830",
"/js/collections.js": "/js/collections.js?id=bdd652ffb9754adf81aa",
"/js/components.js": "/js/components.js?id=7bd6746b7213ee1cfa07",
"/js/components.js": "/js/components.js?id=fba10d02a472976640ed",
"/js/compose.js": "/js/compose.js?id=0dd3798521b8b0ae7b7b",
"/js/compose-classic.js": "/js/compose-classic.js?id=c994899e82ff5a905c81",
"/js/developers.js": "/js/developers.js?id=9bba3be10491e58de952",
"/js/direct.js": "/js/direct.js?id=2e34b51be7aa0b65d7e7",
"/js/direct.js": "/js/direct.js?id=451f2ad256090f24cced",
"/js/discover.js": "/js/discover.js?id=dcc903e7b89832d4fa59",
"/js/hashtag.js": "/js/hashtag.js?id=1ec0defe0d591a4a417e",
"/js/loops.js": "/js/loops.js?id=ae34b77c4cfe1824f5a0",
"/js/memoryprofile.js": "/js/memoryprofile.js?id=e20bf2a59c5003f56b97",
"/js/memoryprofile.js": "/js/memoryprofile.js?id=887eab75f4812600de26",
"/js/mode-dot.js": "/js/mode-dot.js?id=af90766521763f9d8f71",
"/js/profile.js": "/js/profile.js?id=3dcca2e2eea64488f62d",
"/js/profile.js": "/js/profile.js?id=d3b094b75a4e7c10f794",
"/js/profile-directory.js": "/js/profile-directory.js?id=e63d5f2c6f2d5710a8bd",
"/js/quill.js": "/js/quill.js?id=4769f11fc9a6c32dde50",
"/js/rempos.js": "/js/rempos.js?id=010cf1d2a75deb514060",
"/js/rempro.js": "/js/rempro.js?id=4608c76dfbfb77f259a7",
"/js/rempos.js": "/js/rempos.js?id=a4705303116584cfa3f2",
"/js/rempro.js": "/js/rempro.js?id=a498e954ac466ac3ad61",
"/js/search.js": "/js/search.js?id=68156d01717856b142ea",
"/js/status.js": "/js/status.js?id=a9ea8019f0891c03452b",
"/js/status.js": "/js/status.js?id=c41642dc3bcae66c96ab",
"/js/stories.js": "/js/stories.js?id=94721cbf86251179e7f7",
"/js/story-compose.js": "/js/story-compose.js?id=34b9e22a404107475f2e",
"/js/theme-monokai.js": "/js/theme-monokai.js?id=498da56ab16309f277e7",
"/js/timeline.js": "/js/timeline.js?id=21857714f1b6b5a1bc78"
"/js/theme-monokai.js": "/js/theme-monokai.js?id=8842103833ba4861bcfa",
"/js/timeline.js": "/js/timeline.js?id=63de948e4a82abac8a87"
}

Wyświetl plik

@ -103,31 +103,31 @@ window.App.util = {
}
return Math.floor(seconds) + "s";
}),
timeAhead: (function(ts) {
timeAhead: (function(ts, short = true) {
let date = Date.parse(ts);
let diff = date - Date.parse(new Date());
let seconds = Math.floor((diff) / 1000);
let interval = Math.floor(seconds / 63072000);
if (interval >= 1) {
return interval + "y";
return interval + (short ? "y" : " years");
}
interval = Math.floor(seconds / 604800);
if (interval >= 1) {
return interval + "w";
return interval + (short ? "w" : " weeks");
}
interval = Math.floor(seconds / 86400);
if (interval >= 1) {
return interval + "d";
return interval + (short ? "d" : " days");
}
interval = Math.floor(seconds / 3600);
if (interval >= 1) {
return interval + "h";
return interval + (short ? "h" : " hours");
}
interval = Math.floor(seconds / 60);
if (interval >= 1) {
return interval + "m";
return interval + (short ? "m" : " minutes");
}
return Math.floor(seconds) + "s";
return Math.floor(seconds) + (short ? "s" : " seconds");
}),
rewriteLinks: (function(i) {
@ -234,7 +234,8 @@ window.App.util = {
'filter-willow': 'brightness(1.2) contrast(.85) saturate(.05) sepia(.2)',
'filter-xpro-ii': 'sepia(.45) contrast(1.25) brightness(1.75) saturate(1.3) hue-rotate(-5deg)'
},
emoji: ['😂','💯','❤️','🙌','👏','👌','😍','😯','😢','😅','😁','🙂','😎','😀','🤣','😃','😄','😆','😉','😊','😋','😘','😗','😙','😚','🤗','🤩','🤔','🤨','😐','😑','😶','🙄','😏','😣','😥','😮','🤐','😪','😫','😴','😌','😛','😜','😝','🤤','😒','😓','😔','😕','🙃','🤑','😲','🙁','😖','😞','😟','😤','😭','😦','😧','😨','😩','🤯','😬','😰','😱','😳','🤪','😵','😡','😠','🤬','😷','🤒','🤕','🤢','🤮','🤧','😇','🤠','🤡','🤥','🤫','🤭','🧐','🤓','😈','👿','👹','👺','💀','👻','👽','🤖','💩','😺','😸','😹','😻','😼','😽','🙀','😿','😾','🤲','👐','🤝','👍','👎','👊','✊','🤛','🤜','🤞','✌️','🤟','🤘','👈','👉','👆','👇','☝️','✋','🤚','🖐','🖖','👋','🤙','💪','🖕','✍️','🙏','💍','💄','💋','👄','👅','👂','👃','👣','👁','👀','🧠','🗣','👤','👥'
emoji: [
'😂','💯','❤️','🙌','👏','👌','😍','😯','😢','😅','😁','🙂','😎','😀','🤣','😃','😄','😆','😉','😊','😋','😘','😗','😙','😚','🤗','🤩','🤔','🤨','😐','😑','😶','🙄','😏','😣','😥','😮','🤐','😪','😫','😴','😌','😛','😜','😝','🤤','😒','😓','😔','😕','🙃','🤑','😲','🙁','😖','😞','😟','😤','😭','😦','😧','😨','😩','🤯','😬','😰','😱','😳','🤪','😵','😡','😠','🤬','😷','🤒','🤕','🤢','🤮','🤧','😇','🤠','🤡','🤥','🤫','🤭','🧐','🤓','😈','👿','👹','👺','💀','👻','👽','🤖','💩','😺','😸','😹','😻','😼','😽','🙀','😿','😾','🤲','👐','🤝','👍','👎','👊','✊','🤛','🤜','🤞','✌️','🤟','🤘','👈','👉','👆','👇','☝️','✋','🤚','🖐','🖖','👋','🤙','💪','🖕','✍️','🙏','💍','💄','💋','👄','👅','👂','👃','👣','👁','👀','🧠','🗣','👤','👥'
],
embed: {
post: (function(url, caption = true, likes = false, layout = 'full') {

Wyświetl plik

@ -22,56 +22,60 @@
<a :href="getProfileUrl(n.account)" class="font-weight-bold text-dark word-break" data-placement="bottom" data-toggle="tooltip" :title="n.account.username">{{n.account.local == false ? '@':''}}{{truncate(n.account.username)}}</a> liked your <a class="font-weight-bold" v-bind:href="getPostUrl(n.status)">post</a>.
</p>
</div>
<div v-else-if="n.type == 'comment'">
<p class="my-0">
<a :href="getProfileUrl(n.account)" class="font-weight-bold text-dark word-break" data-placement="bottom" data-toggle="tooltip" :title="n.account.username">{{n.account.local == false ? '@':''}}{{truncate(n.account.username)}}</a> commented on your <a class="font-weight-bold" v-bind:href="getPostUrl(n.status)">post</a>.
</p>
</div>
<div v-else-if="n.type == 'group:comment'">
<p class="my-0">
<a :href="getProfileUrl(n.account)" class="font-weight-bold text-dark word-break" :title="n.account.username">{{n.account.local == false ? '@':''}}{{truncate(n.account.username)}}</a> commented on your <a class="font-weight-bold" v-bind:href="n.group_post_url">group post</a>.
</p>
</div>
<div v-else-if="n.type == 'story:react'">
<p class="my-0">
<a :href="getProfileUrl(n.account)" class="font-weight-bold text-dark word-break" :title="n.account.username">{{n.account.local == false ? '@':''}}{{truncate(n.account.username)}}</a> reacted to your <a class="font-weight-bold" v-bind:href="'/account/direct/t/'+n.account.id">story</a>.
</p>
</div>
<div v-else-if="n.type == 'story:comment'">
<p class="my-0">
<a :href="getProfileUrl(n.account)" class="font-weight-bold text-dark word-break" :title="n.account.username">{{n.account.local == false ? '@':''}}{{truncate(n.account.username)}}</a> commented on your <a class="font-weight-bold" v-bind:href="'/account/direct/t/'+n.account.id">story</a>.
</p>
</div>
<div v-else-if="n.type == 'mention'">
<p class="my-0">
<a :href="getProfileUrl(n.account)" class="font-weight-bold text-dark word-break" data-placement="bottom" data-toggle="tooltip" :title="n.account.username">{{n.account.local == false ? '@':''}}{{truncate(n.account.username)}}</a> <a class="font-weight-bold" v-bind:href="mentionUrl(n.status)">mentioned</a> you.
</p>
</div>
<div v-else-if="n.type == 'follow'">
<p class="my-0">
<a :href="getProfileUrl(n.account)" class="font-weight-bold text-dark word-break" data-placement="bottom" data-toggle="tooltip" :title="n.account.username">{{n.account.local == false ? '@':''}}{{truncate(n.account.username)}}</a> followed you.
</p>
</div>
<div v-else-if="n.type == 'share'">
<p class="my-0">
<a :href="getProfileUrl(n.account)" class="font-weight-bold text-dark word-break" data-placement="bottom" data-toggle="tooltip" :title="n.account.username">{{n.account.local == false ? '@':''}}{{truncate(n.account.username)}}</a> shared your <a class="font-weight-bold" v-bind:href="getPostUrl(n.status)">post</a>.
</p>
</div>
<div v-else-if="n.type == 'modlog'">
<p class="my-0">
<a :href="getProfileUrl(n.account)" class="font-weight-bold text-dark word-break" :title="n.account.username">{{truncate(n.account.username)}}</a> updated a <a class="font-weight-bold" v-bind:href="n.modlog.url">modlog</a>.
</p>
</div>
<div v-else-if="n.type == 'tagged'">
<p class="my-0">
<a :href="getProfileUrl(n.account)" class="font-weight-bold text-dark word-break" :title="n.account.username">{{n.account.local == false ? '@':''}}{{truncate(n.account.username)}}</a> tagged you in a <a class="font-weight-bold" v-bind:href="n.tagged.post_url">post</a>.
</p>
</div>
<div v-else-if="n.type == 'direct'">
<p class="my-0">
<a :href="getProfileUrl(n.account)" class="font-weight-bold text-dark word-break" :title="n.account.username">{{n.account.local == false ? '@':''}}{{truncate(n.account.username)}}</a> sent a <a class="font-weight-bold" v-bind:href="'/account/direct/t/'+n.account.id">dm</a>.
</p>
</div>
<div class="align-items-center">
<span class="small text-muted" data-toggle="tooltip" data-placement="bottom" :title="n.created_at">{{timeAgo(n.created_at)}}</span>
</div>
@ -105,7 +109,7 @@
</a>
</div> -->
<div v-else>
<a class="btn btn-outline-primary py-0 font-weight-bold" :href="viewContext(n)">View</a>
<a v-if="viewContext(n) != '/'" class="btn btn-outline-primary py-0 font-weight-bold" :href="viewContext(n)">View</a>
</div>
</div>
</div>
@ -209,6 +213,9 @@ export default {
}
return true;
});
let ids = data.map(n => n.id);
this.notificationMaxId = Math.max(...ids);
this.notifications.push(...data);
this.notificationCursor++;
$state.loaded();

Wyświetl plik

@ -35,7 +35,7 @@
<div class="d-flex d-md-none align-items-center justify-content-between card-header bg-white w-100">
<div class="d-flex">
<div class="status-avatar mr-2" @click="redirect(profileUrl)">
<img :src="statusAvatar" width="24px" height="24px" style="border-radius:12px;" class="cursor-pointer">
<img :src="statusAvatar" width="24px" height="24px" style="border-radius:12px;" class="cursor-pointer" onerror="this.onerror=null;this.src='/storage/avatars/default.jpg?v=0';">
</div>
<div class="username">
<span class="username-link font-weight-bold text-dark cursor-pointer" @click="redirect(profileUrl)">{{ statusUsername }}</span>
@ -94,7 +94,7 @@
<div class="d-md-flex d-none align-items-center justify-content-between card-header py-3 bg-white">
<div class="d-flex align-items-center status-username text-truncate">
<div class="status-avatar mr-2" @click="redirect(profileUrl)">
<img :src="statusAvatar" width="24px" height="24px" style="border-radius:12px;" class="cursor-pointer">
<img :src="statusAvatar" width="24px" height="24px" style="border-radius:12px;" class="cursor-pointer" onerror="this.onerror=null;this.src='/storage/avatars/default.jpg?v=0';">
</div>
<div class="username">
<span class="username-link font-weight-bold text-dark cursor-pointer" @click="redirect(profileUrl)">{{ statusUsername }}</span>
@ -157,7 +157,7 @@
</p>
<div class="comments mt-3">
<div v-for="(reply, index) in results" class="pb-4 media" :key="'tl' + reply.id + '_' + index">
<img :src="reply.account.avatar" class="rounded-circle border mr-3" width="42px" height="42px">
<img :src="reply.account.avatar" class="rounded-circle border mr-3" width="42px" height="42px" onerror="this.onerror=null;this.src='/storage/avatars/default.jpg?v=0';">
<div class="media-body">
<div v-if="reply.sensitive == true">
<span class="py-3">
@ -190,7 +190,7 @@
</div>
<div v-if="reply.thread == true" class="comment-thread">
<div v-for="(s, sindex) in reply.replies" class="pb-3 media" :key="'cr' + s.id + '_' + index">
<img :src="s.account.avatar" class="rounded-circle border mr-3" width="25px" height="25px">
<img :src="s.account.avatar" class="rounded-circle border mr-3" width="25px" height="25px" onerror="this.onerror=null;this.src='/storage/avatars/default.jpg?v=0';">
<div class="media-body">
<p class="d-flex justify-content-between align-items-top read-more" style="overflow-y: hidden;">
<span>
@ -315,7 +315,7 @@
<div class="list-group-item border-0 py-1" v-for="(user, index) in likes" :key="'modal_likes_'+index">
<div class="media">
<a :href="user.url">
<img class="mr-3 rounded-circle box-shadow" :src="user.avatar" :alt="user.username + 's avatar'" width="30px">
<img class="mr-3 rounded-circle box-shadow" :src="user.avatar" :alt="user.username + 's avatar'" width="30px" onerror="this.onerror=null;this.src='/storage/avatars/default.jpg?v=0';">
</a>
<div class="media-body">
<p class="mb-0" style="font-size: 14px">
@ -348,7 +348,7 @@
<div class="list-group-item border-0 py-1" v-for="(user, index) in shares" :key="'modal_shares_'+index">
<div class="media">
<a :href="user.url">
<img class="mr-3 rounded-circle box-shadow" :src="user.avatar" :alt="user.username + 's avatar'" width="30px">
<img class="mr-3 rounded-circle box-shadow" :src="user.avatar" :alt="user.username + 's avatar'" width="30px" onerror="this.onerror=null;this.src='/storage/avatars/default.jpg?v=0';">
</a>
<div class="media-body">
<div class="d-inline-block">
@ -382,7 +382,7 @@
<div class="list-group-item border-0 py-1" v-for="(taguser, index) in status.taggedPeople" :key="'modal_taggedpeople_'+index">
<div class="media">
<a :href="'/'+taguser.username">
<img class="mr-3 rounded-circle box-shadow" :src="taguser.avatar" :alt="taguser.username + 's avatar'" width="30px">
<img class="mr-3 rounded-circle box-shadow" :src="taguser.avatar" :alt="taguser.username + 's avatar'" width="30px" onerror="this.onerror=null;this.src='/storage/avatars/default.jpg?v=0';">
</a>
<div class="media-body">
<p class="pt-1 d-flex justify-content-between" style="font-size: 14px">

Wyświetl plik

@ -19,7 +19,7 @@
</div>
<div class="card-body pb-0">
<div class="mt-n5 mb-3">
<img class="rounded-circle p-1 border mt-n4 bg-white shadow" :src="profile.avatar" width="90px" height="90px;">
<img class="rounded-circle p-1 border mt-n4 bg-white shadow" :src="profile.avatar" width="90px" height="90px;" onerror="this.onerror=null;this.src='/storage/avatars/default.jpg?v=0';">
<span class="float-right mt-n1">
<span>
<button v-if="relationship && relationship.following == false" class="btn btn-outline-light py-0 px-3 mt-n1" style="font-size:13px; font-weight: 500;" @click="followProfile();">Follow</button>

Wyświetl plik

@ -12,7 +12,7 @@
<div style="margin-top:-2px;">
<story-component v-if="config.features.stories" :scope="scope"></story-component>
</div>
<div>
<div class="pt-4">
<div v-if="loading" class="text-center" style="padding-top:10px;">
<div class="spinner-border" role="status">
<span class="sr-only">Loading...</span>
@ -106,6 +106,8 @@
size="small"
v-on:status-delete="deleteStatus"
v-on:comment-focus="commentFocus"
v-on:followed="followedAccount"
v-on:unfollowed="unfollowedAccount"
/>
</div>
@ -1067,7 +1069,29 @@
this.feed = this.feed.filter(s => {
return s.id != status;
});
}
},
followedAccount(id) {
this.feed = this.feed.map(s => {
if(s.account.id == id) {
if(s.hasOwnProperty('relationship') && s.relationship.following == false) {
s.relationship.following = true;
}
}
return s;
});
},
unfollowedAccount(id) {
this.feed = this.feed.map(s => {
if(s.account.id == id) {
if(s.hasOwnProperty('relationship') && s.relationship.following == true) {
s.relationship.following = false;
}
}
return s;
});
},
},
beforeDestroy () {

Wyświetl plik

@ -71,6 +71,14 @@
<a v-if="status.place" class="small text-decoration-none text-muted" :href="'/discover/places/'+status.place.id+'/'+status.place.slug" title="Location" data-toggle="tooltip"><i class="fas fa-map-marked-alt"></i> {{status.place.name}}, {{status.place.country}}</a>
</div>
</div>
<div v-if="canFollow(status)">
<span class="px-2"></span>
<button class="btn btn-primary btn-sm font-weight-bold py-1 px-3 rounded-lg" @click="follow(status.account.id)"><i class="far fa-user-plus mr-1"></i> Follow</button>
</div>
<div v-if="status.hasOwnProperty('relationship') && status.relationship.hasOwnProperty('following') && status.relationship.following">
<span class="px-2"></span>
<button class="btn btn-outline-primary btn-sm font-weight-bold py-1 px-3 rounded-lg" @click="unfollow(status.account.id)"><i class="far fa-user-check mr-1"></i> Following</button>
</div>
<div class="text-right" style="flex-grow:1;">
<button class="btn btn-link text-dark py-0" type="button" @click="ctxMenu()">
<span class="fas fa-ellipsis-h text-lighter"></span>
@ -382,6 +390,52 @@
statusDeleted(status) {
this.$emit('status-delete', status);
},
canFollow(status) {
if(!status.hasOwnProperty('relationship')) {
return false;
}
if(!status.hasOwnProperty('account') || !status.account.hasOwnProperty('id')) {
return false;
}
if(status.account.id === this.profile.id) {
return false;
}
return !status.relationship.following;
},
follow(id) {
event.currentTarget.blur();
axios.post('/i/follow', {
item: id
}).then(res => {
this.status.relationship.following = true;
this.$emit('followed', id);
}).catch(err => {
if(err.response.data.message) {
swal('Error', err.response.data.message, 'error');
}
});
},
unfollow(id) {
event.currentTarget.blur();
axios.post('/i/follow', {
item: id
}).then(res => {
this.status.relationship.following = false;
this.$emit('unfollowed', id);
}).catch(err => {
if(err.response.data.message) {
swal('Error', err.response.data.message, 'error');
}
});
}
}
}

Wyświetl plik

@ -14,7 +14,7 @@
>
<b-carousel-slide v-for="(media, index) in status.media_attachments" :key="media.id + '-media'">
<video v-if="media.type == 'Video'" slot="img" class="embed-responsive-item" preload="none" controls loop :alt="media.description" width="100%" height="100%" :poster="media.preview_url">
<video v-if="media.type == 'Video'" slot="img" class="embed-responsive-item" preload="none" controls playsinline loop :alt="media.description" width="100%" height="100%" :poster="media.preview_url">
<source :src="media.url" :type="media.mime">
</video>
@ -72,4 +72,4 @@
export default {
props: ['status']
}
</script>
</script>

Wyświetl plik

@ -13,7 +13,7 @@
:interval="0"
>
<b-carousel-slide v-for="(vid, index) in status.media_attachments" :key="vid.id + '-media'">
<video slot="img" class="embed-responsive-item" preload="none" controls loop :alt="vid.description" width="100%" height="100%" :poster="vid.preview_url">
<video slot="img" class="embed-responsive-item" preload="none" controls playsinline loop :alt="vid.description" width="100%" height="100%" :poster="vid.preview_url">
<source :src="vid.url" :type="vid.mime">
</video>
</b-carousel-slide>
@ -29,7 +29,7 @@
:interval="0"
>
<b-carousel-slide v-for="(vid, index) in status.media_attachments" :key="vid.id + '-media'">
<video slot="img" class="embed-responsive-item" preload="none" controls loop :alt="vid.description" width="100%" height="100%" :poster="vid.preview_url">
<video slot="img" class="embed-responsive-item" preload="none" controls playsinline loop :alt="vid.description" width="100%" height="100%" :poster="vid.preview_url">
<source :src="vid.url" :type="vid.mime">
</video>
</b-carousel-slide>
@ -41,4 +41,4 @@
export default {
props: ['status']
}
</script>
</script>

Wyświetl plik

@ -22,7 +22,7 @@
:alt="altText(status)"/>
</div>
<div v-else class="embed-responsive embed-responsive-16by9">
<video class="video" controls preload="metadata" loop :poster="status.media_attachments[0].preview_url" :data-id="status.id">
<video class="video" controls playsinline preload="metadata" loop :poster="status.media_attachments[0].preview_url" :data-id="status.id">
<source :src="status.media_attachments[0].url" :type="status.media_attachments[0].mime">
</video>
</div>

Wyświetl plik

@ -1,7 +1,6 @@
<?php
return [
'about' => 'O nás',
'help' => 'Nápověda',
'language' => 'Jazyk',
@ -16,5 +15,4 @@ return [
'contact-us' => 'Kontaktujte nás',
'places' => 'Místa',
'profiles' => 'Profily',
];

Wyświetl plik

@ -0,0 +1,11 @@
<?php
return [
'compose' => [
'invalid' => [
'album' => 'Mindestens 1 Foto oder Video muss enthalten sein.',
],
],
];

Wyświetl plik

@ -1,7 +1,6 @@
<?php
return [
'search' => 'Suche',
'home' => 'Heim',
'local' => 'Lokal',
@ -16,5 +15,5 @@ return [
'admin' => 'Administration',
'logout' => 'Abmelden',
'directMessages' => 'Privatnachrichten',
'composePost' => 'Neu',
];

Wyświetl plik

@ -15,5 +15,5 @@ return [
'contact' => 'Kontakt',
'contact-us' => 'Kontaktiere uns',
'places' => 'Orte',
'profiles' => 'Profile',
];

Wyświetl plik

@ -11,6 +11,12 @@ Route::post('i/actor/inbox', 'InstanceActorController@inbox');
Route::get('i/actor/outbox', 'InstanceActorController@outbox');
Route::get('/stories/{username}/{id}', 'StoryController@getActivityObject');
Route::get('.well-known/webfinger', 'FederationController@webfinger')->name('well-known.webfinger');
Route::get('.well-known/nodeinfo', 'FederationController@nodeinfoWellKnown')->name('well-known.nodeinfo');
Route::get('.well-known/host-meta', 'FederationController@hostMeta')->name('well-known.hostMeta');
Route::redirect('.well-known/change-password', '/settings/password');
Route::get('api/nodeinfo/2.0.json', 'FederationController@nodeinfo');
Route::group(['prefix' => 'api'], function() use($middleware) {
Route::group(['prefix' => 'v1'], function() use($middleware) {

Wyświetl plik

@ -90,11 +90,6 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
Auth::routes();
Route::get('.well-known/webfinger', 'FederationController@webfinger')->name('well-known.webfinger');
Route::get('.well-known/nodeinfo', 'FederationController@nodeinfoWellKnown')->name('well-known.nodeinfo');
Route::get('.well-known/host-meta', 'FederationController@hostMeta')->name('well-known.hostMeta');
Route::redirect('.well-known/change-password', '/settings/password');
Route::get('/home', 'HomeController@index')->name('home');
Route::get('discover/c/{slug}', 'DiscoverController@showCategory');
@ -105,7 +100,6 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
Route::group(['prefix' => 'api'], function () {
Route::get('search', 'SearchController@searchAPI');
Route::get('nodeinfo/2.0.json', 'FederationController@nodeinfo');
Route::post('status/view', 'StatusController@storeView');
Route::get('v1/polls/{id}', 'PollController@getPoll');
Route::post('v1/polls/{id}/votes', 'PollController@vote');
@ -251,7 +245,6 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
Route::post('v1/publish', 'StoryController@publishStory');
Route::delete('v1/delete/{id}', 'StoryController@apiV1Delete');
});
});
Route::get('discover/tags/{hashtag}', 'DiscoverController@showTags');