forked from grumpydevelop/federator
refactored ContentNation data-format
- also fixed issue where we didn't retrieve posts from the DB - also fixed issue where posts from db were malformatted - changed contentnation data-converter for comments & votes to support the new data-structure (more consistent, easier to read)
This commit is contained in:
parent
d355b5a7cd
commit
ba88adcebd
4 changed files with 109 additions and 93 deletions
|
@ -89,6 +89,11 @@ class NewContent implements \Federator\Api\APIInterface
|
||||||
|
|
||||||
$input = is_string($_rawInput) ? json_decode($_rawInput, true) : null;
|
$input = is_string($_rawInput) ? json_decode($_rawInput, true) : null;
|
||||||
|
|
||||||
|
|
||||||
|
$dbh = $this->main->getDatabase();
|
||||||
|
$cache = $this->main->getCache();
|
||||||
|
$connector = $this->main->getConnector();
|
||||||
|
|
||||||
$config = $this->main->getConfig();
|
$config = $this->main->getConfig();
|
||||||
$domain = $config['generic']['externaldomain'];
|
$domain = $config['generic']['externaldomain'];
|
||||||
if (!is_array($input)) {
|
if (!is_array($input)) {
|
||||||
|
@ -97,21 +102,16 @@ class NewContent implements \Federator\Api\APIInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($allHeaders['X-Sender'])) {
|
if (isset($allHeaders['X-Sender'])) {
|
||||||
$newActivity = $this->main->getConnector()->jsonToActivity($input);
|
$newActivity = $connector->jsonToActivity($input);
|
||||||
} else {
|
} else {
|
||||||
error_log("NewContent::post No X-Sender header found");
|
error_log("NewContent::post No X-Sender header found");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($newActivity === false) {
|
if ($newActivity === false) {
|
||||||
error_log("NewContent::post couldn't create newActivity");
|
error_log("NewContent::post couldn't create newActivity");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$dbh = $this->main->getDatabase();
|
|
||||||
$cache = $this->main->getCache();
|
|
||||||
$connector = $this->main->getConnector();
|
|
||||||
|
|
||||||
if (!isset($_user)) {
|
if (!isset($_user)) {
|
||||||
$user = $newActivity->getAActor(); // url of the sender https://contentnation.net/username
|
$user = $newActivity->getAActor(); // url of the sender https://contentnation.net/username
|
||||||
$user = str_replace(
|
$user = str_replace(
|
||||||
|
|
|
@ -117,7 +117,7 @@ class Factory
|
||||||
$return = new Common\Undo();
|
$return = new Common\Undo();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
error_log("newActivityFromJson " . print_r($json, true));
|
error_log("newActivityFromJson unsupported type: " . print_r($json, true));
|
||||||
}
|
}
|
||||||
if (isset($return) && $return->fromJson($json) !== null) {
|
if (isset($return) && $return->fromJson($json) !== null) {
|
||||||
return $return;
|
return $return;
|
||||||
|
|
|
@ -43,6 +43,7 @@ class Posts
|
||||||
if ($posts === false) {
|
if ($posts === false) {
|
||||||
$posts = [];
|
$posts = [];
|
||||||
}
|
}
|
||||||
|
echo "Found " . count($posts) . " posts in DB\n";
|
||||||
|
|
||||||
// Only override $min if we found posts in our DB
|
// Only override $min if we found posts in our DB
|
||||||
$remoteMin = $min;
|
$remoteMin = $min;
|
||||||
|
@ -102,15 +103,15 @@ class Posts
|
||||||
*/
|
*/
|
||||||
public static function getPostsFromDb($dbh, $userId, $min = null, $max = null)
|
public static function getPostsFromDb($dbh, $userId, $min = null, $max = null)
|
||||||
{
|
{
|
||||||
$sql = 'SELECT id, user_id, type, object, published FROM posts WHERE user_id = ?';
|
$sql = 'SELECT `id`, `url`, `user_id`, `actor`, `type`, `object`, `to`, `cc`, `published` FROM posts WHERE user_id = ?';
|
||||||
$params = [$userId];
|
$params = [$userId];
|
||||||
$types = 's';
|
$types = 's';
|
||||||
if ($min !== null) {
|
if ($min !== null && $min !== "") {
|
||||||
$sql .= ' AND published >= ?';
|
$sql .= ' AND published >= ?';
|
||||||
$params[] = $min;
|
$params[] = $min;
|
||||||
$types .= 's';
|
$types .= 's';
|
||||||
}
|
}
|
||||||
if ($max !== null) {
|
if ($max !== null && $max !== "") {
|
||||||
$sql .= ' AND published <= ?';
|
$sql .= ' AND published <= ?';
|
||||||
$params[] = $max;
|
$params[] = $max;
|
||||||
$types .= 's';
|
$types .= 's';
|
||||||
|
@ -130,16 +131,33 @@ class Posts
|
||||||
}
|
}
|
||||||
$posts = [];
|
$posts = [];
|
||||||
while ($row = $result->fetch_assoc()) {
|
while ($row = $result->fetch_assoc()) {
|
||||||
if (!empty($row['object'])) {
|
if (isset($row['to']) && $row['to'] !== null) {
|
||||||
$objectData = json_decode($row['object'], true);
|
$row['to'] = json_decode($row['to'], true);
|
||||||
if (is_array($objectData)) {
|
}
|
||||||
// Use the ActivityPub factory to create the APObject
|
if (isset($row['cc']) && $row['cc'] !== null) {
|
||||||
$object = \Federator\Data\ActivityPub\Factory::newActivityFromJson($objectData);
|
$row['cc'] = json_decode($row['cc'], true);
|
||||||
if ($object !== false) {
|
}
|
||||||
$posts[] = $object;
|
if (isset($row['object']) && $row['object'] !== null) {
|
||||||
}
|
$decoded = json_decode($row['object'], true);
|
||||||
|
// Only use decoded value if it's an array/object
|
||||||
|
if (is_array($decoded)) {
|
||||||
|
$row['object'] = $decoded;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (isset($row['published']) && $row['published'] !== null) {
|
||||||
|
// If it's numeric, keep as int. If it's a string, try to parse as ISO 8601.
|
||||||
|
if (is_numeric($row['published'])) {
|
||||||
|
$row['published'] = intval($row['published'], 10);
|
||||||
|
} else {
|
||||||
|
// Try to parse as datetime string
|
||||||
|
$timestamp = strtotime($row['published']);
|
||||||
|
$row['published'] = $timestamp !== false ? $timestamp : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$activity = \Federator\Data\ActivityPub\Factory::newActivityFromJson($row);
|
||||||
|
if ($activity !== false) {
|
||||||
|
$posts[] = $activity;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$stmt->close();
|
$stmt->close();
|
||||||
return $posts;
|
return $posts;
|
||||||
|
|
|
@ -151,6 +151,7 @@ class ContentNation implements Connector
|
||||||
->addTo("https://www.w3.org/ns/activitystreams#Public")
|
->addTo("https://www.w3.org/ns/activitystreams#Public")
|
||||||
->addCC('https://' . $domain . '/' . $userId . '/followers');
|
->addCC('https://' . $domain . '/' . $userId . '/followers');
|
||||||
$create->setURL('https://' . $domain . '/' . $activity['profilename'] . '/' . $activity['name']);
|
$create->setURL('https://' . $domain . '/' . $activity['profilename'] . '/' . $activity['name']);
|
||||||
|
$create->setID('https://' . $domain . '/' . $activity['profilename'] . '/' . $activity['id']);
|
||||||
$apArticle = new \Federator\Data\ActivityPub\Common\Article();
|
$apArticle = new \Federator\Data\ActivityPub\Common\Article();
|
||||||
if (array_key_exists('tags', $activity)) {
|
if (array_key_exists('tags', $activity)) {
|
||||||
foreach ($activity['tags'] as $tag) {
|
foreach ($activity['tags'] as $tag) {
|
||||||
|
@ -207,16 +208,23 @@ class ContentNation implements Connector
|
||||||
$commentJson = $activity;
|
$commentJson = $activity;
|
||||||
$commentJson['type'] = 'Note';
|
$commentJson['type'] = 'Note';
|
||||||
$commentJson['summary'] = $activity['subject'];
|
$commentJson['summary'] = $activity['subject'];
|
||||||
$commentJson['id'] = $activity['id'];
|
$commentJson['id'] = 'https://' . $domain . '/' . $activity['articleOwnerName'] . '/' . $activity['articleName'] . '#' . $activity['id'];
|
||||||
$note = \Federator\Data\ActivityPub\Factory::newFromJson($commentJson, "");
|
$note = \Federator\Data\ActivityPub\Factory::newFromJson($commentJson, "");
|
||||||
if ($note === null) {
|
if ($note === null) {
|
||||||
error_log("ContentNation::getRemotePostsByUser couldn't create comment");
|
error_log("ContentNation::getRemotePostsByUser couldn't create comment");
|
||||||
$comment = new \Federator\Data\ActivityPub\Common\Activity('Comment');
|
$note = new \Federator\Data\ActivityPub\Common\Activity('Comment');
|
||||||
$create->setObject($comment);
|
$create->setObject($note);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
$url = 'https://' . $domain . '/' . $activity['articleOwnerName'] . '/' . $activity['articleName'] . '#' . $note->getID();
|
$note->setID($commentJson['id']);
|
||||||
|
if (!isset($commentJson['parent']) || $commentJson['parent'] === null) {
|
||||||
|
$note->setInReplyTo('https://' . $domain . '/' . $activity['articleOwnerName'] . '/' . $activity['articleName']);
|
||||||
|
} elseif ($replyType === "comment") {
|
||||||
|
$note->setInReplyTo('https://' . $domain . '/' . $activity['articleOwnerName'] . '/' . $activity['articleName'] . "#" . $commentJson['parent']);
|
||||||
|
}
|
||||||
|
$url = 'https://' . $domain . '/' . $activity['articleOwnerName'] . '/' . $activity['articleName'] . '#' . $activity['id'];
|
||||||
$create->setURL($url);
|
$create->setURL($url);
|
||||||
|
$create->setID($url);
|
||||||
$create->setObject($note);
|
$create->setObject($note);
|
||||||
$posts[] = $create;
|
$posts[] = $create;
|
||||||
break; // Comment
|
break; // Comment
|
||||||
|
@ -227,9 +235,9 @@ class ContentNation implements Connector
|
||||||
$like = new \Federator\Data\ActivityPub\Common\Activity($likeType);
|
$like = new \Federator\Data\ActivityPub\Common\Activity($likeType);
|
||||||
$like->setAActor('https://' . $domain . '/' . $userId);
|
$like->setAActor('https://' . $domain . '/' . $userId);
|
||||||
$like->setID($activity['id'])
|
$like->setID($activity['id'])
|
||||||
->setPublished($activity['published'] ?? $activity['timestamp'])
|
->setPublished($activity['published'] ?? $activity['timestamp']);
|
||||||
->addTo("https://www.w3.org/ns/activitystreams#Public")
|
// $like->addTo("https://www.w3.org/ns/activitystreams#Public")
|
||||||
->addCC('https://' . $domain . '/' . $userId . '/followers');
|
// ->addCC('https://' . $domain . '/' . $userId . '/followers');
|
||||||
$like->setSummary(
|
$like->setSummary(
|
||||||
$this->main->translate(
|
$this->main->translate(
|
||||||
$activity['articlelang'],
|
$activity['articlelang'],
|
||||||
|
@ -239,10 +247,8 @@ class ContentNation implements Connector
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
$objectUrl = 'https://' . $domain . '/' . $userId . '/' . $activity['articlename'];
|
$objectUrl = 'https://' . $domain . '/' . $userId . '/' . $activity['articlename'];
|
||||||
if ($activity['comment'] !== '') {
|
|
||||||
$objectUrl .= "#" . $activity['comment'];
|
|
||||||
}
|
|
||||||
$like->setURL($objectUrl . '#' . $activity['id']);
|
$like->setURL($objectUrl . '#' . $activity['id']);
|
||||||
|
$like->setID($objectUrl . '#' . $activity['id']);
|
||||||
$like->setObject($objectUrl);
|
$like->setObject($objectUrl);
|
||||||
$posts[] = $like;
|
$posts[] = $like;
|
||||||
break; // Vote
|
break; // Vote
|
||||||
|
@ -364,6 +370,7 @@ class ContentNation implements Connector
|
||||||
*/
|
*/
|
||||||
public function jsonToActivity(array $jsonData)
|
public function jsonToActivity(array $jsonData)
|
||||||
{
|
{
|
||||||
|
$returnActivity = false;
|
||||||
// Common fields for all activity types
|
// Common fields for all activity types
|
||||||
$ap = [
|
$ap = [
|
||||||
'@context' => 'https://www.w3.org/ns/activitystreams',
|
'@context' => 'https://www.w3.org/ns/activitystreams',
|
||||||
|
@ -372,52 +379,65 @@ class ContentNation implements Connector
|
||||||
'actor' => $jsonData['actor'] ?? null,
|
'actor' => $jsonData['actor'] ?? null,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$config = $this->main->getConfig();
|
||||||
|
$domain = $config['generic']['externaldomain'];
|
||||||
|
|
||||||
|
$ourUrl = 'https://' . $domain;
|
||||||
|
|
||||||
// Extract actorName as the last segment of the actor URL (after the last '/')
|
// Extract actorName as the last segment of the actor URL (after the last '/')
|
||||||
$actorUrl = $jsonData['actor'] ?? null;
|
$actorData = $jsonData['actor'] ?? null;
|
||||||
$actorName = null;
|
$actorName = $actorData['name'] ?? null;
|
||||||
$replaceUrl = null;
|
|
||||||
if (is_array($actorUrl)) {
|
$ap['actor'] = $ourUrl . '/' . $actorName;
|
||||||
$actorUrl = $actorUrl['id'];
|
|
||||||
$replaceUrl = $actorUrl->url ?? null;
|
|
||||||
}
|
|
||||||
if ($actorUrl !== null) {
|
|
||||||
$actorUrlParts = parse_url($actorUrl);
|
|
||||||
if (isset($actorUrlParts['path'])) {
|
|
||||||
$pathSegments = array_values(array_filter(explode('/', $actorUrlParts['path'])));
|
|
||||||
$actorName = end($pathSegments);
|
|
||||||
// Build replaceUrl as scheme://host if both are set
|
|
||||||
if (isset($actorUrlParts['scheme'], $actorUrlParts['host'])) {
|
|
||||||
$replaceUrl = $actorUrlParts['scheme'] . '://' . $actorUrlParts['host'];
|
|
||||||
} else {
|
|
||||||
$replaceUrl = $actorUrl;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$actorName = $actorUrl;
|
|
||||||
$replaceUrl = $actorUrl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$ap['actor'] = $actorUrl;
|
|
||||||
|
|
||||||
// Handle specific fields based on the type
|
// Handle specific fields based on the type
|
||||||
switch ($jsonData['type']) {
|
switch ($jsonData['type']) {
|
||||||
case 'comment':
|
case 'comment':
|
||||||
|
$commentId = $jsonData['object']['id'] ?? null;
|
||||||
|
$articleName = $jsonData['object']['articleName'] ?? null;
|
||||||
|
$articleOwnerName = $jsonData['object']['articleOwnerName'] ?? null;
|
||||||
// Set Create-level fields
|
// Set Create-level fields
|
||||||
$ap['published'] = $jsonData['object']['published'] ?? null;
|
$ap['published'] = $jsonData['object']['published'] ?? null;
|
||||||
$ap['url'] = $jsonData['object']['url'] ?? null;
|
$ap['id'] = $ourUrl . "/" . $articleOwnerName . "/" . $articleName . "#" . $commentId;
|
||||||
$ap['to'] = ['https://www.w3.org/ns/activitystreams#Public'];
|
$ap['url'] = $ourUrl . "/" . $articleOwnerName . "/" . $articleName . "#" . $commentId;
|
||||||
$ap['cc'] = [$jsonData['related']['cc']['followers'] ?? null];
|
$ap['type'] = 'Create';
|
||||||
|
|
||||||
// Set object as Note with only required fields
|
|
||||||
$ap['object'] = [
|
$ap['object'] = [
|
||||||
'id' => $jsonData['object']['id'] ?? null,
|
|
||||||
'type' => 'Note',
|
'type' => 'Note',
|
||||||
'content' => $jsonData['object']['content'] ?? '',
|
'id' => $ourUrl . "/" . $articleOwnerName . "/" . $articleName . "#" . $commentId,
|
||||||
'summary' => $jsonData['object']['summary'] ?? '',
|
'content' => $jsonData['object']['content'] ?? null,
|
||||||
|
'summary' => $jsonData['object']['summary'] ?? null,
|
||||||
];
|
];
|
||||||
|
$ap['cc'] = ['https://www.w3.org/ns/activitystreams#Public'];
|
||||||
|
if (isset($jsonData['options'])) {
|
||||||
|
if (isset($jsonData['options']['informFollowers'])) {
|
||||||
|
if ($jsonData['options']['informFollowers'] === true) {
|
||||||
|
if ($actorName != $articleOwnerName) {
|
||||||
|
$ap['to'][] = $ourUrl . '/' . $articleOwnerName;
|
||||||
|
}
|
||||||
|
$ap['to'][] = $ourUrl . '/' . $actorName . '/followers';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$replyType = $jsonData['object']['inReplyTo']['type'] ?? null;
|
||||||
|
if ($replyType === "article") {
|
||||||
|
$ap['object']['inReplyTo'] = $ourUrl . "/" . $articleOwnerName . "/" . $articleName;
|
||||||
|
} elseif ($replyType === "comment") {
|
||||||
|
$ap['object']['inReplyTo'] = $ourUrl . "/" . $articleOwnerName . "/" . $articleName . "#" . $jsonData['object']['inReplyTo']['id'];
|
||||||
|
} else {
|
||||||
|
error_log("ContentNation::jsonToActivity unknown inReplyTo type: {$replyType}");
|
||||||
|
}
|
||||||
|
$returnActivity = \Federator\Data\ActivityPub\Factory::newActivityFromJson($ap);
|
||||||
|
$returnActivity->setID($ap['id']);
|
||||||
|
$returnActivity->setURL($ap['url']);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'vote':
|
case 'vote':
|
||||||
$ap['id'] .= "_$actorName";
|
$votedOn = $jsonData['object']['type'] ?? null;
|
||||||
|
$articleName = $jsonData['object']['articleName'] ?? null;
|
||||||
|
$articleOwnerName = $jsonData['object']['articleOwnerName'] ?? null;
|
||||||
|
$voteId = $jsonData['object']['id'] ?? null;
|
||||||
|
$ap['id'] = $ourUrl . '/' . $articleOwnerName . '/' . $articleName . '#' . $voteId;
|
||||||
|
$ap['url'] = $ourUrl . '/' . $articleOwnerName . '/' . $articleName . '#' . $voteId;
|
||||||
if (
|
if (
|
||||||
isset($jsonData['vote']['type']) &&
|
isset($jsonData['vote']['type']) &&
|
||||||
strtolower($jsonData['vote']['type']) === 'undo'
|
strtolower($jsonData['vote']['type']) === 'undo'
|
||||||
|
@ -432,7 +452,10 @@ class ContentNation implements Connector
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
$objectId = $jsonData['object']['id'] ?? null;
|
$objectId = $ourUrl . '/' . $articleOwnerName . '/' . $articleName;
|
||||||
|
if ($votedOn === "comment") {
|
||||||
|
$objectId .= '#' . $jsonData['object']['commentId'];
|
||||||
|
}
|
||||||
|
|
||||||
$ap['object'] = $objectId;
|
$ap['object'] = $objectId;
|
||||||
|
|
||||||
|
@ -462,43 +485,18 @@ class ContentNation implements Connector
|
||||||
'author' => $jsonData['object']['author'] ?? null,
|
'author' => $jsonData['object']['author'] ?? null,
|
||||||
];
|
];
|
||||||
} */
|
} */
|
||||||
|
|
||||||
|
$returnActivity = \Federator\Data\ActivityPub\Factory::newActivityFromJson($ap);
|
||||||
|
$returnActivity->setID($ap['id']);
|
||||||
|
$returnActivity->setURL($ap['url']);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// Handle unsupported types or fallback to default behavior
|
// Handle unsupported types or fallback to default behavior
|
||||||
throw new \InvalidArgumentException("Unsupported activity type: {$jsonData['type']}");
|
throw new \InvalidArgumentException("ContentNation::jsonToActivity Unsupported activity type: {$jsonData['type']}");
|
||||||
}
|
}
|
||||||
|
|
||||||
$config = $this->main->getConfig();
|
return $returnActivity;
|
||||||
$domain = $config['generic']['externaldomain'];
|
|
||||||
/**
|
|
||||||
* Recursively replace strings in an array.
|
|
||||||
*
|
|
||||||
* @param string $search
|
|
||||||
* @param string $replace
|
|
||||||
* @param array<mixed, mixed> $array
|
|
||||||
* @return array<mixed, mixed>
|
|
||||||
*/
|
|
||||||
function array_str_replace_recursive(string $search, string $replace, array $array): array
|
|
||||||
{
|
|
||||||
foreach ($array as $key => $value) {
|
|
||||||
if (is_array($value)) {
|
|
||||||
$array[$key] = array_str_replace_recursive($search, $replace, $value);
|
|
||||||
} elseif (is_string($value)) {
|
|
||||||
$array[$key] = str_replace($search, $replace, $value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $array;
|
|
||||||
}
|
|
||||||
if (is_string($replaceUrl)) {
|
|
||||||
$ap = array_str_replace_recursive(
|
|
||||||
$replaceUrl,
|
|
||||||
'https://' . $domain,
|
|
||||||
$ap
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return \Federator\Data\ActivityPub\Factory::newActivityFromJson($ap);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Add table
Reference in a new issue