[PLUGIN][ActivityPub] Notify mentions in tags
This commit is contained in:
parent
9d0b39e680
commit
78fddaf86a
@ -60,7 +60,7 @@ class Notification extends Component
|
|||||||
*/
|
*/
|
||||||
public function onNewNotification(Actor $sender, Activity $activity, array $ids_already_known = [], ?string $reason = null): bool
|
public function onNewNotification(Actor $sender, Activity $activity, array $ids_already_known = [], ?string $reason = null): bool
|
||||||
{
|
{
|
||||||
$targets = $activity->getNotificationTargets($ids_already_known, sender_id: $sender->getId());
|
$targets = $activity->getNotificationTargets(ids_already_known: $ids_already_known, sender_id: $sender->getId());
|
||||||
$this->notify($sender, $activity, $targets, $reason);
|
$this->notify($sender, $activity, $targets, $reason);
|
||||||
|
|
||||||
return Event::next;
|
return Event::next;
|
||||||
@ -83,8 +83,16 @@ class Notification extends Component
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO: use https://symfony.com/doc/current/notifier.html
|
// TODO: use https://symfony.com/doc/current/notifier.html
|
||||||
|
DB::persist(Entity\Notification::create([
|
||||||
|
'activity_id' => $activity->getId(),
|
||||||
|
'target_id' => $target->getId(),
|
||||||
|
'reason' => $reason,
|
||||||
|
]));
|
||||||
} else {
|
} else {
|
||||||
$remote_targets[] = $target;
|
// We have no authority nor responsibility of notifying remote actors of a remote actor's doing
|
||||||
|
if ($sender->getIsLocal()) {
|
||||||
|
$remote_targets[] = $target;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,9 +73,13 @@ class Tag extends Component
|
|||||||
*/
|
*/
|
||||||
public function onProcessNoteContent(Note $note, string $content, string $content_type, array $extra_args): bool
|
public function onProcessNoteContent(Note $note, string $content, string $content_type, array $extra_args): bool
|
||||||
{
|
{
|
||||||
$matched_tags = [];
|
if ($extra_args['TagProcessed'] ?? false) {
|
||||||
|
return Event::next;
|
||||||
|
}
|
||||||
// XXX: We remove <span> because when content is in html the tag comes as #<span>hashtag</span>
|
// XXX: We remove <span> because when content is in html the tag comes as #<span>hashtag</span>
|
||||||
preg_match_all(self::TAG_REGEX, str_replace('<span>', '', $content), $matched_tags, \PREG_SET_ORDER);
|
$content = str_replace('<span>', '', $content);
|
||||||
|
$matched_tags = [];
|
||||||
|
preg_match_all(self::TAG_REGEX, $content, $matched_tags, \PREG_SET_ORDER);
|
||||||
$matched_tags = array_unique(F\map($matched_tags, fn ($m) => $m[2]));
|
$matched_tags = array_unique(F\map($matched_tags, fn ($m) => $m[2]));
|
||||||
foreach ($matched_tags as $match) {
|
foreach ($matched_tags as $match) {
|
||||||
$tag = self::ensureValid($match);
|
$tag = self::ensureValid($match);
|
||||||
|
@ -34,6 +34,7 @@ namespace Plugin\ActivityPub\Controller;
|
|||||||
|
|
||||||
use App\Core\Controller;
|
use App\Core\Controller;
|
||||||
use App\Core\DB\DB;
|
use App\Core\DB\DB;
|
||||||
|
use App\Core\Event;
|
||||||
use function App\Core\I18n\_m;
|
use function App\Core\I18n\_m;
|
||||||
use App\Core\Log;
|
use App\Core\Log;
|
||||||
use App\Core\Router\Router;
|
use App\Core\Router\Router;
|
||||||
@ -164,7 +165,9 @@ class Inbox extends Controller
|
|||||||
$ap_actor->getActorId(),
|
$ap_actor->getActorId(),
|
||||||
Discovery::normalize($actor->getNickname() . '@' . parse_url($ap_actor->getInboxUri(), PHP_URL_HOST)),
|
Discovery::normalize($actor->getNickname() . '@' . parse_url($ap_actor->getInboxUri(), PHP_URL_HOST)),
|
||||||
);
|
);
|
||||||
|
Event::handle('NewNotification', [$actor, $ap_act->getActivity(), [], "{$actor->getNickname()} mentioned you in a note"]);
|
||||||
DB::flush();
|
DB::flush();
|
||||||
|
|
||||||
dd($ap_act, $act = $ap_act->getActivity(), $act->getActor(), $act->getObject());
|
dd($ap_act, $act = $ap_act->getActivity(), $act->getActor(), $act->getObject());
|
||||||
|
|
||||||
return new TypeResponse($type, status: 202);
|
return new TypeResponse($type, status: 202);
|
||||||
|
@ -34,6 +34,7 @@ namespace Plugin\ActivityPub\Util\Model;
|
|||||||
|
|
||||||
use ActivityPhp\Type;
|
use ActivityPhp\Type;
|
||||||
use ActivityPhp\Type\AbstractObject;
|
use ActivityPhp\Type\AbstractObject;
|
||||||
|
use App\Core\Cache;
|
||||||
use App\Core\DB\DB;
|
use App\Core\DB\DB;
|
||||||
use App\Core\Event;
|
use App\Core\Event;
|
||||||
use App\Core\GSFile;
|
use App\Core\GSFile;
|
||||||
@ -43,6 +44,7 @@ use App\Core\Log;
|
|||||||
use App\Core\Router\Router;
|
use App\Core\Router\Router;
|
||||||
use App\Entity\Language;
|
use App\Entity\Language;
|
||||||
use App\Entity\Note as GSNote;
|
use App\Entity\Note as GSNote;
|
||||||
|
use App\Entity\NoteTag;
|
||||||
use App\Util\Common;
|
use App\Util\Common;
|
||||||
use App\Util\Exception\ClientException;
|
use App\Util\Exception\ClientException;
|
||||||
use App\Util\Exception\DuplicateFoundException;
|
use App\Util\Exception\DuplicateFoundException;
|
||||||
@ -53,6 +55,7 @@ use App\Util\TemporaryFile;
|
|||||||
use Component\Attachment\Entity\ActorToAttachment;
|
use Component\Attachment\Entity\ActorToAttachment;
|
||||||
use Component\Attachment\Entity\AttachmentToNote;
|
use Component\Attachment\Entity\AttachmentToNote;
|
||||||
use Component\Conversation\Conversation;
|
use Component\Conversation\Conversation;
|
||||||
|
use Component\Tag\Tag;
|
||||||
use DateTime;
|
use DateTime;
|
||||||
use DateTimeInterface;
|
use DateTimeInterface;
|
||||||
use Exception;
|
use Exception;
|
||||||
@ -200,8 +203,39 @@ class Note extends Model
|
|||||||
// Assign conversation to this note
|
// Assign conversation to this note
|
||||||
Conversation::assignLocalConversation($obj, $reply_to);
|
Conversation::assignLocalConversation($obj, $reply_to);
|
||||||
|
|
||||||
// Need file and note ids for the next step
|
$object_mentions_ids = [];
|
||||||
Event::handle('ProcessNoteContent', [$obj, $obj->getRendered(), $obj->getContentType(), $process_note_content_extra_args = []]);
|
foreach ($type_note->get('tag') as $ap_tag) {
|
||||||
|
switch ($ap_tag->get('type')) {
|
||||||
|
case 'Mention':
|
||||||
|
try {
|
||||||
|
$actor = ActivityPub::getActorByUri($ap_tag->get('href'));
|
||||||
|
if ($actor->getIsLocal()) {
|
||||||
|
$object_mentions_ids[] = $actor->getId();
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
Log::debug('ActivityPub->Model->Note->fromJson->getActorByUri', [$e]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'Hashtag':
|
||||||
|
$match = ltrim($ap_tag->get('name'), '#');
|
||||||
|
$tag = Tag::ensureValid($match);
|
||||||
|
$canonical_tag = $ap_tag->get('canonical') ?? Tag::canonicalTag($tag, \is_null($lang_id = $obj->getLanguageId()) ? null : Language::getById($lang_id)->getLocale());
|
||||||
|
DB::persist(NoteTag::create([
|
||||||
|
'tag' => $tag,
|
||||||
|
'canonical' => $canonical_tag,
|
||||||
|
'note_id' => $obj->getId(),
|
||||||
|
'use_canonical' => $ap_tag->get('canonical') ?? false,
|
||||||
|
]));
|
||||||
|
Cache::pushList("tag-{$canonical_tag}", $obj);
|
||||||
|
foreach (Tag::cacheKeys($canonical_tag) as $key) {
|
||||||
|
Cache::delete($key);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$obj->setObjectMentionsIds($object_mentions_ids);
|
||||||
|
// The content would be non-sanitized text/html
|
||||||
|
Event::handle('ProcessNoteContent', [$obj, $obj->getRendered(), $obj->getContentType(), $process_note_content_extra_args = ['TagProcessed' => true]]);
|
||||||
|
|
||||||
if ($processed_attachments !== []) {
|
if ($processed_attachments !== []) {
|
||||||
foreach ($processed_attachments as [$a, $fname]) {
|
foreach ($processed_attachments as [$a, $fname]) {
|
||||||
@ -268,9 +302,10 @@ class Note extends Model
|
|||||||
// Hashtags
|
// Hashtags
|
||||||
foreach ($object->getTags() as $hashtag) {
|
foreach ($object->getTags() as $hashtag) {
|
||||||
$attr['tag'][] = [
|
$attr['tag'][] = [
|
||||||
'type' => 'Hashtag',
|
'type' => 'Hashtag',
|
||||||
'href' => $hashtag->getUrl(type: Router::ABSOLUTE_URL),
|
'href' => $hashtag->getUrl(type: Router::ABSOLUTE_URL),
|
||||||
'name' => "#{$hashtag->getTag()}",
|
'name' => "#{$hashtag->getTag()}",
|
||||||
|
'canonical' => $hashtag->getCanonical(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -380,15 +380,25 @@ class Note extends Entity
|
|||||||
/**
|
/**
|
||||||
* @return array of ids of Actors
|
* @return array of ids of Actors
|
||||||
*/
|
*/
|
||||||
|
private array $object_mentions_ids = [];
|
||||||
|
public function setObjectMentionsIds(array $mentions): self
|
||||||
|
{
|
||||||
|
$this->object_mentions_ids = $mentions;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
public function getNotificationTargetIds(array $ids_already_known = [], ?int $sender_id = null): array
|
public function getNotificationTargetIds(array $ids_already_known = [], ?int $sender_id = null): array
|
||||||
{
|
{
|
||||||
$target_ids = [];
|
$target_ids = $this->object_mentions_ids ?? [];
|
||||||
if (!\array_key_exists('object', $ids_already_known)) {
|
if ($target_ids === []) {
|
||||||
$mentions = Formatting::findMentions($this->getContent(), $this->getActor());
|
if (!\array_key_exists('object', $ids_already_known)) {
|
||||||
foreach ($mentions as $mention) {
|
$mentions = Formatting::findMentions($this->getContent(), $this->getActor());
|
||||||
foreach ($mention['mentioned'] as $m) {
|
foreach ($mentions as $mention) {
|
||||||
$target_ids[] = $m->getId();
|
foreach ($mention['mentioned'] as $m) {
|
||||||
|
$target_ids[] = $m->getId();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
$target_ids = $ids_already_known['object'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -263,7 +263,7 @@ abstract class Formatting
|
|||||||
// php-intl is highly recommended...
|
// php-intl is highly recommended...
|
||||||
if (!\function_exists('transliterator_transliterate')) {
|
if (!\function_exists('transliterator_transliterate')) {
|
||||||
$str = preg_replace('/[^\pL\pN]/u', '', $str);
|
$str = preg_replace('/[^\pL\pN]/u', '', $str);
|
||||||
$str = mb_convert_case($str, \MB_CASE_LOWER, 'UTF-8');
|
$str = mb_convert_case($str, MB_CASE_LOWER, 'UTF-8');
|
||||||
return mb_substr($str, 0, $length);
|
return mb_substr($str, 0, $length);
|
||||||
}
|
}
|
||||||
$str = transliterator_transliterate('Any-Latin;' // any charset to latin compatible
|
$str = transliterator_transliterate('Any-Latin;' // any charset to latin compatible
|
||||||
@ -290,6 +290,8 @@ abstract class Formatting
|
|||||||
public static function findMentions(string $text, Actor $actor): array
|
public static function findMentions(string $text, Actor $actor): array
|
||||||
{
|
{
|
||||||
$mentions = [];
|
$mentions = [];
|
||||||
|
// XXX: We remove <span> because when content is in html the tag comes as #<span>hashtag</span>
|
||||||
|
$text = str_replace('<span>', '', $text);
|
||||||
if (Event::handle('StartFindMentions', [$actor, $text, &$mentions])) {
|
if (Event::handle('StartFindMentions', [$actor, $text, &$mentions])) {
|
||||||
$matches = self::findMentionsRaw($text, '@');
|
$matches = self::findMentionsRaw($text, '@');
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user