mirror of
https://github.com/pixelfed/pixelfed.git
synced 2024-12-27 15:33:17 +00:00
312 lines
9.3 KiB
PHP
312 lines
9.3 KiB
PHP
|
<?php
|
||
|
|
||
|
namespace App\Services\Groups;
|
||
|
|
||
|
use App\Models\Group;
|
||
|
use App\Models\GroupPost;
|
||
|
use App\Models\GroupComment;
|
||
|
use Cache;
|
||
|
use Purify;
|
||
|
use Illuminate\Support\Facades\Redis;
|
||
|
use League\Fractal;
|
||
|
use App\Util\ActivityPub\Helpers;
|
||
|
use League\Fractal\Serializer\ArraySerializer;
|
||
|
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
|
||
|
use App\Transformer\Api\GroupPostTransformer;
|
||
|
use App\Services\ActivityPubFetchService;
|
||
|
use Illuminate\Support\Facades\Validator;
|
||
|
use App\Rules\ValidUrl;
|
||
|
|
||
|
class GroupActivityPubService
|
||
|
{
|
||
|
const CACHE_KEY = 'pf:services:groups:ap:';
|
||
|
|
||
|
public static function fetchGroup($url, $saveOnFetch = true)
|
||
|
{
|
||
|
$group = Group::where('remote_url', $url)->first();
|
||
|
if($group) {
|
||
|
return $group;
|
||
|
}
|
||
|
|
||
|
$res = ActivityPubFetchService::get($url);
|
||
|
if(!$res) {
|
||
|
return $res;
|
||
|
}
|
||
|
$json = json_decode($res, true);
|
||
|
$group = self::validateGroup($json);
|
||
|
if(!$group) {
|
||
|
return false;
|
||
|
}
|
||
|
if($saveOnFetch) {
|
||
|
return self::storeGroup($group);
|
||
|
}
|
||
|
return $group;
|
||
|
}
|
||
|
|
||
|
public static function fetchGroupPost($url, $saveOnFetch = true)
|
||
|
{
|
||
|
$group = GroupPost::where('remote_url', $url)->first();
|
||
|
|
||
|
if($group) {
|
||
|
return $group;
|
||
|
}
|
||
|
|
||
|
$res = ActivityPubFetchService::get($url);
|
||
|
if(!$res) {
|
||
|
return 'invalid res';
|
||
|
}
|
||
|
$json = json_decode($res, true);
|
||
|
if(!$json) {
|
||
|
return 'invalid json';
|
||
|
}
|
||
|
if(isset($json['inReplyTo'])) {
|
||
|
$comment = self::validateGroupComment($json);
|
||
|
return self::storeGroupComment($comment);
|
||
|
}
|
||
|
|
||
|
$group = self::validateGroupPost($json);
|
||
|
if($saveOnFetch) {
|
||
|
return self::storeGroupPost($group);
|
||
|
}
|
||
|
return $group;
|
||
|
}
|
||
|
|
||
|
public static function validateGroup($obj)
|
||
|
{
|
||
|
$validator = Validator::make($obj, [
|
||
|
'@context' => 'required',
|
||
|
'id' => ['required', 'url', new ValidUrl],
|
||
|
'type' => 'required|in:Group',
|
||
|
'preferredUsername' => 'required',
|
||
|
'name' => 'required',
|
||
|
'url' => ['sometimes', 'url', new ValidUrl],
|
||
|
'inbox' => ['required', 'url', new ValidUrl],
|
||
|
'outbox' => ['required', 'url', new ValidUrl],
|
||
|
'followers' => ['required', 'url', new ValidUrl],
|
||
|
'attributedTo' => 'required',
|
||
|
'summary' => 'sometimes',
|
||
|
'publicKey' => 'required',
|
||
|
'publicKey.id' => 'required',
|
||
|
'publicKey.owner' => ['required', 'url', 'same:id', new ValidUrl],
|
||
|
'publicKey.publicKeyPem' => 'required',
|
||
|
]);
|
||
|
|
||
|
if($validator->fails()) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return $validator->validated();
|
||
|
}
|
||
|
|
||
|
public static function validateGroupPost($obj)
|
||
|
{
|
||
|
$validator = Validator::make($obj, [
|
||
|
'@context' => 'required',
|
||
|
'id' => ['required', 'url', new ValidUrl],
|
||
|
'type' => 'required|in:Page,Note',
|
||
|
'to' => 'required|array',
|
||
|
'to.*' => ['required', 'url', new ValidUrl],
|
||
|
'cc' => 'sometimes|array',
|
||
|
'cc.*' => ['sometimes', 'url', new ValidUrl],
|
||
|
'url' => ['sometimes', 'url', new ValidUrl],
|
||
|
'attributedTo' => 'required',
|
||
|
'name' => 'sometimes',
|
||
|
'target' => 'sometimes',
|
||
|
'audience' => 'sometimes',
|
||
|
'inReplyTo' => 'sometimes',
|
||
|
'content' => 'sometimes',
|
||
|
'mediaType' => 'sometimes',
|
||
|
'sensitive' => 'sometimes',
|
||
|
'attachment' => 'sometimes',
|
||
|
'published' => 'required',
|
||
|
]);
|
||
|
|
||
|
if($validator->fails()) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return $validator->validated();
|
||
|
}
|
||
|
|
||
|
public static function validateGroupComment($obj)
|
||
|
{
|
||
|
$validator = Validator::make($obj, [
|
||
|
'@context' => 'required',
|
||
|
'id' => ['required', 'url', new ValidUrl],
|
||
|
'type' => 'required|in:Note',
|
||
|
'to' => 'required|array',
|
||
|
'to.*' => ['required', 'url', new ValidUrl],
|
||
|
'cc' => 'sometimes|array',
|
||
|
'cc.*' => ['sometimes', 'url', new ValidUrl],
|
||
|
'url' => ['sometimes', 'url', new ValidUrl],
|
||
|
'attributedTo' => 'required',
|
||
|
'name' => 'sometimes',
|
||
|
'target' => 'sometimes',
|
||
|
'audience' => 'sometimes',
|
||
|
'inReplyTo' => 'sometimes',
|
||
|
'content' => 'sometimes',
|
||
|
'mediaType' => 'sometimes',
|
||
|
'sensitive' => 'sometimes',
|
||
|
'published' => 'required',
|
||
|
]);
|
||
|
|
||
|
if($validator->fails()) {
|
||
|
return $validator->errors();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return $validator->validated();
|
||
|
}
|
||
|
|
||
|
public static function getGroupFromPostActivity($groupPost)
|
||
|
{
|
||
|
if(isset($groupPost['audience']) && is_string($groupPost['audience'])) {
|
||
|
return $groupPost['audience'];
|
||
|
}
|
||
|
|
||
|
if(
|
||
|
isset(
|
||
|
$groupPost['target'],
|
||
|
$groupPost['target']['type'],
|
||
|
$groupPost['target']['attributedTo']
|
||
|
) && $groupPost['target']['type'] == 'Collection'
|
||
|
) {
|
||
|
return $groupPost['target']['attributedTo'];
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
public static function getActorFromPostActivity($groupPost)
|
||
|
{
|
||
|
if(!isset($groupPost['attributedTo'])) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
$field = $groupPost['attributedTo'];
|
||
|
|
||
|
if(is_string($field)) {
|
||
|
return $field;
|
||
|
}
|
||
|
|
||
|
if(is_array($field) && count($field) === 1) {
|
||
|
if(
|
||
|
isset(
|
||
|
$field[0]['id'],
|
||
|
$field[0]['type']
|
||
|
) &&
|
||
|
$field[0]['type'] === 'Person' &&
|
||
|
is_string($field[0]['id'])
|
||
|
) {
|
||
|
return $field[0]['id'];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
public static function getCaptionFromPostActivity($groupPost)
|
||
|
{
|
||
|
if(!isset($groupPost['name']) && isset($groupPost['content'])) {
|
||
|
return Purify::clean(strip_tags($groupPost['content']));
|
||
|
}
|
||
|
|
||
|
if(isset($groupPost['name'], $groupPost['content'])) {
|
||
|
return Purify::clean(strip_tags($groupPost['name'])) . Purify::clean(strip_tags($groupPost['content']));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public static function getSensitiveFromPostActivity($groupPost)
|
||
|
{
|
||
|
if(!isset($groupPost['sensitive'])) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if(isset($groupPost['sensitive']) && !is_bool($groupPost['sensitive'])) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return boolval($groupPost['sensitive']);
|
||
|
}
|
||
|
|
||
|
public static function storeGroup($activity)
|
||
|
{
|
||
|
$group = new Group;
|
||
|
$group->profile_id = null;
|
||
|
$group->category_id = 1;
|
||
|
$group->name = $activity['name'] ?? 'Untitled Group';
|
||
|
$group->description = isset($activity['summary']) ? Purify::clean($activity['summary']) : null;
|
||
|
$group->is_private = false;
|
||
|
$group->local_only = false;
|
||
|
$group->metadata = [];
|
||
|
$group->local = false;
|
||
|
$group->remote_url = $activity['id'];
|
||
|
$group->inbox_url = $activity['inbox'];
|
||
|
$group->activitypub = true;
|
||
|
$group->save();
|
||
|
|
||
|
return $group;
|
||
|
}
|
||
|
|
||
|
public static function storeGroupPost($groupPost)
|
||
|
{
|
||
|
$groupUrl = self::getGroupFromPostActivity($groupPost);
|
||
|
if(!$groupUrl) {
|
||
|
return;
|
||
|
}
|
||
|
$group = self::fetchGroup($groupUrl, true);
|
||
|
if(!$group) {
|
||
|
return;
|
||
|
}
|
||
|
$actorUrl = self::getActorFromPostActivity($groupPost);
|
||
|
$actor = Helpers::profileFetch($actorUrl);
|
||
|
$caption = self::getCaptionFromPostActivity($groupPost);
|
||
|
$sensitive = self::getSensitiveFromPostActivity($groupPost);
|
||
|
$model = GroupPost::firstOrCreate(
|
||
|
[
|
||
|
'remote_url' => $groupPost['id'],
|
||
|
], [
|
||
|
'group_id' => $group->id,
|
||
|
'profile_id' => $actor->id,
|
||
|
'type' => 'text',
|
||
|
'caption' => $caption,
|
||
|
'visibility' => 'public',
|
||
|
'is_nsfw' => $sensitive,
|
||
|
]
|
||
|
);
|
||
|
return $model;
|
||
|
}
|
||
|
|
||
|
public static function storeGroupComment($groupPost)
|
||
|
{
|
||
|
$groupUrl = self::getGroupFromPostActivity($groupPost);
|
||
|
if(!$groupUrl) {
|
||
|
return;
|
||
|
}
|
||
|
$group = self::fetchGroup($groupUrl, true);
|
||
|
if(!$group) {
|
||
|
return;
|
||
|
}
|
||
|
$actorUrl = self::getActorFromPostActivity($groupPost);
|
||
|
$actor = Helpers::profileFetch($actorUrl);
|
||
|
$caption = self::getCaptionFromPostActivity($groupPost);
|
||
|
$sensitive = self::getSensitiveFromPostActivity($groupPost);
|
||
|
$parentPost = self::fetchGroupPost($groupPost['inReplyTo']);
|
||
|
$model = GroupComment::firstOrCreate(
|
||
|
[
|
||
|
'remote_url' => $groupPost['id'],
|
||
|
], [
|
||
|
'group_id' => $group->id,
|
||
|
'profile_id' => $actor->id,
|
||
|
'status_id' => $parentPost->id,
|
||
|
'type' => 'text',
|
||
|
'caption' => $caption,
|
||
|
'visibility' => 'public',
|
||
|
'is_nsfw' => $sensitive,
|
||
|
'local' => $actor->private_key != null
|
||
|
]
|
||
|
);
|
||
|
return $model;
|
||
|
}
|
||
|
}
|