forked from grumpydevelop/federator
conditionally convert article to note
- fix bug in which inReplyTo isn't correctly set from contentnation-comments - added dio/article which has functions to convert article to note based on new file - added formatsupport.json to manage special cases (f.e. includes which servers can handle articles)
This commit is contained in:
parent
30c577c82f
commit
10dec5ebd3
9 changed files with 203 additions and 23 deletions
8
formatsupport.json
Normal file
8
formatsupport.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"activitypub": {
|
||||
"article": [
|
||||
"localhost",
|
||||
"writefreely.org"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -62,7 +62,8 @@
|
|||
|
||||
const headers = {
|
||||
...(session ? { "X-Session": session } : {}),
|
||||
...(profile ? { "X-Profile": profile } : {})
|
||||
...(profile ? { "X-Profile": profile } : {}),
|
||||
"HTTP_HOST": "localhost",
|
||||
};
|
||||
|
||||
fetch("http://localhost/" + targetLink, {
|
||||
|
|
|
@ -241,6 +241,11 @@ class Inbox implements \Federator\Api\FedUsers\FedUsersInterface
|
|||
return false;
|
||||
}
|
||||
|
||||
$atPos = strpos($_recipientId, '@');
|
||||
if ($atPos !== false) {
|
||||
$_recipientId = substr($_recipientId, 0, $atPos);
|
||||
}
|
||||
|
||||
// get recipient
|
||||
$recipient = \Federator\DIO\User::getUserByName(
|
||||
$dbh,
|
||||
|
@ -265,11 +270,8 @@ class Inbox implements \Federator\Api\FedUsers\FedUsersInterface
|
|||
|
||||
switch ($type) {
|
||||
case 'Follow':
|
||||
$success = false;
|
||||
$actor = $inboxActivity->getAActor();
|
||||
if ($actor !== '') {
|
||||
$success = \Federator\DIO\Followers::addExternalFollow($dbh, $inboxActivity->getID(), $user->id, $recipient->id);
|
||||
}
|
||||
$success = \Federator\DIO\Followers::addExternalFollow($dbh, $inboxActivity->getID(), $user->id, $recipient->id);
|
||||
|
||||
if ($success === false) {
|
||||
error_log("Inbox::postForUser: Failed to add follower for user $user->id");
|
||||
}
|
||||
|
|
|
@ -346,25 +346,37 @@ class NewContent implements \Federator\Api\APIInterface
|
|||
break;
|
||||
case 'Note':
|
||||
// Undo Note (remove note)
|
||||
if (method_exists($object, 'getID')) {
|
||||
$noteId = $object->getID();
|
||||
\Federator\DIO\Posts::deletePost($dbh, $noteId);
|
||||
}
|
||||
$noteId = $object->getID();
|
||||
\Federator\DIO\Posts::deletePost($dbh, $noteId);
|
||||
|
||||
break;
|
||||
case 'Article':
|
||||
// Undo Article (remove article)
|
||||
if (method_exists($object, 'getID')) {
|
||||
$articleId = $object->getID();
|
||||
\Federator\DIO\Posts::deletePost($dbh, $articleId);
|
||||
// also remove latest saved article-update
|
||||
\Federator\DIO\Posts::deletePost($dbh, $articleId . '#update');
|
||||
$idPart = strrchr($recipient->id, '@');
|
||||
if ($idPart === false) {
|
||||
error_log("NewContent::postForUser Error in Undo Article. $recipient->id, recipient ID is not valid");
|
||||
return false;
|
||||
} else {
|
||||
$targetUrl = ltrim($idPart, '@');
|
||||
|
||||
if ($object instanceof \Federator\Data\ActivityPub\Common\Article) {
|
||||
$object = \Federator\DIO\Article::conditionalConvertToNote($object, $targetUrl);
|
||||
$newActivity->setObject($object);
|
||||
} else {
|
||||
error_log("NewContent::postForUser Error in Undo Article for recipient $recipient->id, object is not an Article");
|
||||
}
|
||||
}
|
||||
$articleId = $object->getID();
|
||||
\Federator\DIO\Posts::deletePost($dbh, $articleId);
|
||||
// also remove latest saved article-update
|
||||
\Federator\DIO\Posts::deletePost($dbh, $articleId . '#update');
|
||||
|
||||
break;
|
||||
}
|
||||
} else if (is_string($object)) {
|
||||
\Federator\DIO\Posts::deletePost($dbh, $object);
|
||||
} else {
|
||||
error_log("NewContent::postForUser Error in Undo for user $user->id, object is not a string or object");
|
||||
error_log("NewContent::postForUser Error in Undo for recipient $recipient->id, object is not a string or object");
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -376,7 +388,7 @@ class NewContent implements \Federator\Api\APIInterface
|
|||
// \Federator\DIO\Votes::addVote($dbh, $user->id, $targetId, 'like');
|
||||
\Federator\DIO\Posts::savePost($dbh, $user->id, $newActivity);
|
||||
} else {
|
||||
error_log("NewContent::postForUser Error in Add Like/Dislike for user $user->id, targetId is not a string");
|
||||
error_log("NewContent::postForUser Error in Add Like/Dislike for recipient $recipient->id, targetId is not a string");
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
@ -387,7 +399,27 @@ class NewContent implements \Federator\Api\APIInterface
|
|||
if (is_object($object)) {
|
||||
switch ($object->getType()) {
|
||||
case 'Note':
|
||||
\Federator\DIO\Posts::savePost($dbh, $user->id, $newActivity);
|
||||
|
||||
break;
|
||||
case 'Article':
|
||||
$idPart = strrchr($recipient->id, '@');
|
||||
if ($idPart === false) {
|
||||
error_log("NewContent::postForUser Error in Create/Update Article. $recipient->id, recipient ID is not valid");
|
||||
return false;
|
||||
} else {
|
||||
$targetUrl = ltrim($idPart, '@');
|
||||
|
||||
if ($object instanceof \Federator\Data\ActivityPub\Common\Article) {
|
||||
$object = \Federator\DIO\Article::conditionalConvertToNote($object, $targetUrl);
|
||||
$newActivity->setObject($object);
|
||||
} else {
|
||||
error_log("NewContent::postForUser Error in Create/Update Article for recipient $recipient->id, object is not an Article");
|
||||
}
|
||||
}
|
||||
\Federator\DIO\Posts::savePost($dbh, $user->id, $newActivity);
|
||||
|
||||
break;
|
||||
default:
|
||||
\Federator\DIO\Posts::savePost($dbh, $user->id, $newActivity);
|
||||
break;
|
||||
|
|
|
@ -376,6 +376,17 @@ class APObject implements \JsonSerializable
|
|||
$this->summary = $summary;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* get summary
|
||||
*
|
||||
* @return string summary
|
||||
*/
|
||||
public function getSummary()
|
||||
{
|
||||
return $this->summary;
|
||||
}
|
||||
|
||||
/**
|
||||
* set type
|
||||
*
|
||||
|
@ -459,6 +470,16 @@ class APObject implements \JsonSerializable
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* get name
|
||||
*
|
||||
* @return string name
|
||||
*/
|
||||
public function getName() : string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* add Image
|
||||
*
|
||||
|
|
68
php/federator/dio/article.php
Normal file
68
php/federator/dio/article.php
Normal file
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2024 Sascha Nitsch (grumpydeveloper) https://contentnation.net/@grumpydevelop
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* @author Yannis Vogel (vogeldevelopment)
|
||||
**/
|
||||
|
||||
namespace Federator\DIO;
|
||||
|
||||
/**
|
||||
* IO functions related to articles
|
||||
*/
|
||||
class Article
|
||||
{
|
||||
/**
|
||||
* Convert an Article to a Note
|
||||
*
|
||||
* @param \Federator\Data\ActivityPub\Common\Article $article
|
||||
* @return \Federator\Data\ActivityPub\Common\Note
|
||||
* The generated note
|
||||
*/
|
||||
public static function convertToNote($article)
|
||||
{
|
||||
$note = new \Federator\Data\ActivityPub\Common\Note();
|
||||
$note->setId($article->getId())
|
||||
->setURL($article->getURL());
|
||||
$note->setContent($article->getContent());
|
||||
$note->setSummary($article->getSummary());
|
||||
$note->setPublished($article->getPublished());
|
||||
$note->setName($article->getName());
|
||||
$note->setAttributedTo($article->getAttributedTo());
|
||||
foreach ($article->getTo() as $to) {
|
||||
$note->addTo($to);
|
||||
}
|
||||
foreach ($article->getCc() as $cc) {
|
||||
$note->addCc($cc);
|
||||
}
|
||||
return $note;
|
||||
}
|
||||
|
||||
/** Conditionally convert article to a note
|
||||
*
|
||||
* @param \Federator\Data\ActivityPub\Common\Article $article
|
||||
* @param string $targetUrl
|
||||
* The target URL for the activity (e.g. mastodon.social)
|
||||
* @return \Federator\Data\ActivityPub\Common\Note|\Federator\Data\ActivityPub\Common\Article
|
||||
* The generated note on success, false on failure
|
||||
*/
|
||||
public static function conditionalConvertToNote($article, $targetUrl)
|
||||
{
|
||||
$supportFile = file_get_contents(PROJECT_ROOT . '/formatsupport.json');
|
||||
if ($supportFile === false) {
|
||||
error_log("Article::conditionalConvertToNote Failed to read support file for article conversion.");
|
||||
return $article; // Fallback to original article if file read fails
|
||||
}
|
||||
$supportlist = json_decode($supportFile, true);
|
||||
|
||||
if (
|
||||
!isset($supportlist['activitypub']['article']) ||
|
||||
!is_array($supportlist['activitypub']['article']) ||
|
||||
!in_array($targetUrl, $supportlist['activitypub']['article'], true)
|
||||
) {
|
||||
return self::convertToNote($article); // Articles are not supported for this target
|
||||
}
|
||||
return $article; // Articles are supported, return as is
|
||||
}
|
||||
}
|
|
@ -393,11 +393,11 @@ class Followers
|
|||
*
|
||||
* @param \mysqli $dbh database handle
|
||||
* @param string $followId the follow ID to use (should be an external url)
|
||||
* @param string $sourceUser source user id
|
||||
* @param string $sourceUserId source user id
|
||||
* @param string $targetUserId target user id
|
||||
* @return boolean true on success, false on failure
|
||||
*/
|
||||
public static function addExternalFollow($dbh, $followId, $sourceUser, $targetUserId)
|
||||
public static function addExternalFollow($dbh, $followId, $sourceUserId, $targetUserId)
|
||||
{
|
||||
// Check if we already follow this user
|
||||
$sql = 'select id from follows where source_user = ? and target_user = ?';
|
||||
|
@ -405,7 +405,7 @@ class Followers
|
|||
if ($stmt === false) {
|
||||
throw new \Federator\Exceptions\ServerError("Followers::addExternalFollow Failed to prepare statement");
|
||||
}
|
||||
$stmt->bind_param("ss", $sourceUser, $targetUserId);
|
||||
$stmt->bind_param("ss", $sourceUserId, $targetUserId);
|
||||
$foundId = 0;
|
||||
$ret = $stmt->bind_result($foundId);
|
||||
$stmt->execute();
|
||||
|
@ -423,7 +423,7 @@ class Followers
|
|||
if ($stmt === false) {
|
||||
throw new \Federator\Exceptions\ServerError("Followers::addExternalFollow Failed to prepare insert statement");
|
||||
}
|
||||
$stmt->bind_param("sss", $followId, $sourceUser, $targetUserId);
|
||||
$stmt->bind_param("sss", $followId, $sourceUserId, $targetUserId);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
return true;
|
||||
|
|
|
@ -78,11 +78,59 @@ class Posts
|
|||
}
|
||||
}
|
||||
|
||||
$originUrl = 'localhost';
|
||||
if (isset($_SERVER['HTTP_HOST'])) {
|
||||
$originUrl = $_SERVER['HTTP_HOST']; // origin of our request - e.g. mastodon
|
||||
} elseif (isset($_SERVER['HTTP_ORIGIN'])) {
|
||||
$origin = $_SERVER['HTTP_ORIGIN'];
|
||||
$parsed = parse_url($origin);
|
||||
if (isset($parsed) && isset($parsed['host'])) {
|
||||
$parsedHost = $parsed['host'];
|
||||
if (is_string($parsedHost) && $parsedHost !== "") {
|
||||
$originUrl = $parsedHost;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isset($originUrl) || $originUrl === "") {
|
||||
$originUrl = 'localhost'; // Fallback to localhost if no origin is set
|
||||
}
|
||||
|
||||
// save posts to DB
|
||||
foreach ($posts as $post) {
|
||||
if ($post->getID() !== "") {
|
||||
self::savePost($dbh, $userid, $post);
|
||||
}
|
||||
switch (strtolower($post->getType())) {
|
||||
case 'undo':
|
||||
$object = $post->getObject();
|
||||
if (is_object($object)) {
|
||||
if (strtolower($object->getType()) === 'article') {
|
||||
if ($object instanceof \Federator\Data\ActivityPub\Common\Article) {
|
||||
$object = \Federator\DIO\Article::conditionalConvertToNote($object, $originUrl);
|
||||
$post->setObject($object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'create':
|
||||
case 'update':
|
||||
$object = $post->getObject();
|
||||
if (is_object($object)) {
|
||||
if (strtolower($object->getType()) === 'article') {
|
||||
if ($object instanceof \Federator\Data\ActivityPub\Common\Article) {
|
||||
$object = \Federator\DIO\Article::conditionalConvertToNote($object, $originUrl);
|
||||
$post->setObject($object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($cache !== null) {
|
||||
|
|
|
@ -635,9 +635,9 @@ class ContentNation implements Connector
|
|||
}
|
||||
$replyType = $jsonData['object']['inReplyTo']['type'] ?? null;
|
||||
if ($replyType === "article") {
|
||||
$returnJson['object']['inReplyTo'] = $ourUrl . "/" . $articleOwnerName . "/" . $articleName;
|
||||
$returnJson['inReplyTo'] = $ourUrl . "/" . $articleOwnerName . "/" . $articleName;
|
||||
} elseif ($replyType === "comment") {
|
||||
$returnJson['object']['inReplyTo'] = $ourUrl . "/" . $articleOwnerName . "/" . $articleName . "#" . $jsonData['object']['inReplyTo']['id'];
|
||||
$returnJson['inReplyTo'] = $ourUrl . "/" . $articleOwnerName . "/" . $articleName . "#" . $jsonData['object']['inReplyTo']['id'];
|
||||
} else {
|
||||
error_log("ContentNation::generateObjectJson for comment - unknown inReplyTo type: {$replyType}");
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue