implemented the mention system

This commit is contained in:
Ghostie 2025-01-08 20:47:12 -05:00
parent a9a22984d2
commit 2c6d0b4404
12 changed files with 263 additions and 43 deletions

View File

@ -33,6 +33,36 @@ class ActionsPost
];
}
preg_match_all ("/@([a-zA-Z0-9_]+)(@[a-zA-Z0-9_.-]+)?/", $request->get ("content"), $mention_matches);
$mentions = $mention_matches [0];
$processed_mentions = [];
foreach ($mentions as $mention)
{
$ats = explode ("@", $mention);
$actor = null;
if (count ($ats) == 2)
{
// it's a local user
$actor = Actor::where ("preferredUsername", $ats [1])->first ();
if (!$actor)
continue;
}
else
{
$actor = Actor::where ("local_actor_id", $mention)->first ();
if (!$actor)
continue;
}
$processed_mentions[] = [
"type" => "Mention",
"href" => $actor->actor_id,
"name" => $mention
];
}
$processed_content = Str::markdown ($request->get ("content"));
$attachments = [];
@ -57,7 +87,8 @@ class ActionsPost
"content" => $processed_content,
"attachments" => $attachments,
"inReplyTo" => $request->inReplyTo ?? null,
"tags" => $processed_tags
"tags" => $processed_tags,
"mentions" => $processed_mentions
];
}
@ -96,6 +127,7 @@ class ActionsPost
"attachments" => $processed ["attachments"],
"inReplyTo" => $processed ["inReplyTo"] ?? null,
"tags" => $processed ["tags"] ?? null,
"mentions" => $processed ["mentions"] ?? null
]
]);
}

View File

@ -4,6 +4,7 @@ namespace App\Http\Controllers\AP;
use App\Models\Note;
use App\Models\NoteAttachment;
use App\Models\NoteMention;
use App\Models\Announcement;
use App\Models\User;
use App\Models\Actor;
@ -208,6 +209,7 @@ class APOutboxController extends Controller
/* if (!$response || $response->getStatusCode () < 200 || $response->getStatusCode () >= 300)
return response ()->json ([ "error" => "failed to post activity" ], 500); */
Log::info ($follow_activity);
$follow_activity->delete ();
return [
"success" => "unfollowed"
@ -368,8 +370,27 @@ class APOutboxController extends Controller
}
}
$create_activity = TypeActivity::craft_create ($actor, $note);
if (isset ($request ["mentions"]))
{
foreach ($request ["mentions"] as $mention)
{
$mention_exists = NoteMention::where ("note_id", $note->id)->where ("object", $mention ["href"])->first ();
if ($mention_exists)
continue;
$object = TypeActor::actor_exists ($mention ["href"]);
if (!$object)
// we don't obtain actors when we are just mentioning them
continue;
$mention = NoteMention::create ([
"note_id" => $note->id,
"actor_id" => $object->id
]);
}
}
$create_activity = TypeActivity::craft_create ($actor, $note);
$note->activity_id = $create_activity->id;
$note->save ();

View File

@ -44,10 +44,6 @@ class PostActivityJob implements ShouldQueue
if ($this->should_sign)
{
$crafted_activity ["to"] = [
"https://www.w3.org/ns/activitystreams#Public",
];
$crafted_activity ["cc"] = [
$this->actor->following
];

View File

@ -12,12 +12,16 @@ class Activity extends Model
"type",
"object",
"target",
"summary"
"summary",
"to",
"cc"
];
protected $casts = [
"object" => "array",
"target" => "array"
"target" => "array",
"to" => "array",
"cc" => "array"
];
public function setObjectAttribute ($value)
@ -25,6 +29,16 @@ class Activity extends Model
$this->attributes["object"] = json_encode ($value, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRESERVE_ZERO_FRACTION);
}
public function setToAttribute ($value)
{
$this->attributes["to"] = json_encode ($value, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRESERVE_ZERO_FRACTION);
}
public function setCcAttribute ($value)
{
$this->attributes["cc"] = json_encode ($value, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRESERVE_ZERO_FRACTION);
}
public function actor ()
{
return $this->belongsTo (Actor::class);

View File

@ -19,8 +19,25 @@ class Note extends Model
"attributedTo",
"content",
"tag",
"to",
"cc"
];
protected $casts = [
"to" => "array",
"cc" => "array"
];
public function setToAttribute ($value)
{
$this->attributes["to"] = json_encode ($value, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRESERVE_ZERO_FRACTION);
}
public function setCcAttribute ($value)
{
$this->attributes["cc"] = json_encode ($value, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRESERVE_ZERO_FRACTION);
}
public function get_activity ()
{
return $this->hasOne (Activity::class, "id", "activity_id");
@ -57,6 +74,11 @@ class Note extends Model
return $this->belongsToMany (Hashtag::class, "note_hashtag");
}
public function get_mentions ()
{
return $this->hasMany (NoteMention::class);
}
public function attachments ()
{
return $this->hasMany (NoteAttachment::class);

View File

@ -0,0 +1,23 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class NoteMention extends Model
{
protected $fillable = [
"note_id",
"actor_id"
];
public function note ()
{
return $this->belongsTo (Note::class);
}
public function actor ()
{
return $this->belongsTo (Actor::class);
}
}

View File

@ -21,6 +21,8 @@ class TypeActivity {
"type" => $activity->type,
"actor" => $activity->actor,
"object" => $activity->object,
"to" => $activity->to,
"cc" => $activity->cc,
"published" => $activity->created_at,
];
@ -96,6 +98,7 @@ class TypeActivity {
{
case "Note":
$create_activity->object = TypeNote::build_response ($fields);
$create_activity->cc = $fields["cc"];
break;
}

View File

@ -366,6 +366,7 @@ class TypeActor {
return TypeActor::actor_process_ordered_collection ($actor->featured);
}
// TODO: Move this to TypeOrderedCollection
public static function actor_process_ordered_collection ($collection_link)
{
$items = [];

View File

@ -7,7 +7,7 @@ use App\Models\Hashtag;
use App\Models\Actor;
use App\Models\Activity;
use App\Models\NoteAttachment;
use App\Models\NoteMention;
use GuzzleHttp\Client;
use Illuminate\Support\Facades\Log;
@ -27,12 +27,8 @@ class TypeNote
"updated" => $note->updated_at,
"url" => $note->url,
"attributedTo" => $note->attributedTo,
"to" => [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc" => [
$author->following
],
"to" => $note->to,
"cc" => $note->cc,
"content" => $note->content
];
@ -56,6 +52,18 @@ class TypeNote
];
}
$mentions = $note->get_mentions ()->get ();
foreach ($mentions as $mention)
{
$response ["tag"] [] = [
"type" => "Mention",
"href" => $mention->actor->actor_id,
"name" => $mention->actor->local_actor_id ?? "@" . $mention->actor->preferredUsername
];
}
Log::info (json_encode ($response));
return $response;
}
@ -74,7 +82,12 @@ class TypeNote
"summary" => $request ["summary"] ?? null,
"attributedTo" => $actor->actor_id,
"content" => $request ["content"] ?? null,
"tag" => $request ["tag"] ?? null
"tag" => $request ["tag"] ?? null,
// TODO: This should change when I implement visibilities and private notes
"cc" => [
$actor->followers
]
]);
$note->url = route ('posts.show', $note->id);
@ -135,37 +148,61 @@ class TypeNote
}
}
if (isset ($request ["tag"]) && $request ["tag"])
{
foreach ($request ["tag"] as $tag)
{
if ($tag ["type"] != "Hashtag")
continue;
$tag_name = $tag ["name"];
$hashtag_exists = Hashtag::where ("name", $tag_name)->first ();
if ($hashtag_exists)
{
$note->get_hashtags ()->attach ($hashtag_exists->id);
continue;
}
$hashtag = Hashtag::create ([
"name" => $tag_name
]);
$note->get_hashtags ()->attach ($hashtag->id);
}
}
if ($request ["inReplyTo"])
{
$parent_exists = Note::where ("note_id", $request ["inReplyTo"])->first ();
if (!$parent_exists)
$parent_exists = TypeNote::obtain_external ($request ["inReplyTo"]);
$note->in_reply_to = $parent_exists ? $parent_exists->note_id : null;
}
if (isset ($request ["tag"]) && $request ["tag"])
{
foreach ($request ["tag"] as $tag)
{
$parent = TypeNote::obtain_external ($request ["inReplyTo"]);
if ($parent)
$note->in_reply_to = $parent->note_id;
// TODO: refactor this, this code is shit but I want to get first working first
switch ($tag ["type"])
{
case "Hashtag":
$tag_name = $tag ["name"];
$hashtag_exists = Hashtag::where ("name", $tag_name)->first ();
if ($hashtag_exists)
{
$note->get_hashtags ()->attach ($hashtag_exists->id);
continue;
}
$hashtag = Hashtag::create ([
"name" => $tag_name
]);
$note->get_hashtags ()->attach ($hashtag->id);
break;
case "Mention":
$mention_name = $tag["name"];
$mention_actor = null;
$actor_exists = Actor::where ("local_actor_id", $mention_name)->first ();
if (!$actor_exists)
{
// let's check if maybe it's local
$processed_name = explode ("@", $mention_name);
if (count ($processed_name) < 2)
continue;
$actor_exists = Actor::where ("preferredUsername", $processed_name [1])->first ();
if (!$actor_exists)
continue;
}
$mention = NoteMention::create ([
"note_id" => $note->id,
"actor_id" => $actor_exists->id
]);
break;
}
}
}

View File

@ -0,0 +1,40 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('activities', function (Blueprint $table) {
$table->json ("to")->default (json_encode (["https://www.w3.org/ns/activitystreams#Public"], JSON_UNESCAPED_SLASHES))->nullable ();
$table->json ("cc")->default (json_encode ([], JSON_UNESCAPED_SLASHES))->nullable ();
});
Schema::table ("notes", function (Blueprint $table) {
$table->json ("to")->default (json_encode (["https://www.w3.org/ns/activitystreams#Public"], JSON_UNESCAPED_SLASHES))->nullable ();
$table->json ("cc")->default (json_encode ([], JSON_UNESCAPED_SLASHES))->nullable ();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('activities', function (Blueprint $table) {
$table->dropColumn ("to");
$table->dropColumn ("cc");
});
Schema::table ("notes", function (Blueprint $table) {
$table->dropColumn ("to");
$table->dropColumn ("cc");
});
}
};

View File

@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('note_mentions', function (Blueprint $table) {
$table->id();
$table->foreignId ("note_id")->constrained()->onDelete("cascade");
$table->foreignId ("actor_id")->constrained()->onDelete("cascade");
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('note_mentions');
}
};

View File

@ -317,7 +317,7 @@
</div>
<div class="inner">
<p>
<b>{{ $actor->name }} has <span class="count">{{ count ($actor->get_posts ()) }}</span> posts.</b>
<b>{{ $actor->name }} has <span class="count">{{ $actor->get_posts ()->total () }}</span> posts.</b>
</p>
@if (auth ()->user () && auth ()->user ()->is ($user))