Compare commits
42 commits
develop
...
mastodon-s
Author | SHA1 | Date | |
---|---|---|---|
![]() |
61f6fe3e7b | ||
![]() |
2ae81a3748 | ||
![]() |
097f871ed6 | ||
![]() |
c1cf2a6be8 | ||
![]() |
d99479c188 | ||
![]() |
4cc9cfdc8c | ||
![]() |
474631dff2 | ||
![]() |
207d876254 | ||
![]() |
9b3ae63c7e | ||
![]() |
96bb1efe16 | ||
![]() |
8891234617 | ||
![]() |
90fe8fab5c | ||
![]() |
f8539e479e | ||
![]() |
10dec5ebd3 | ||
![]() |
30c577c82f | ||
![]() |
5c90b4cfc9 | ||
![]() |
7a5870de95 | ||
![]() |
572bb376c1 | ||
![]() |
62cfd6ef0d | ||
![]() |
ba88adcebd | ||
![]() |
d355b5a7cd | ||
![]() |
767f51cc5b | ||
![]() |
6cf9a030a4 | ||
![]() |
49a4bee76a | ||
![]() |
da18d37a79 | ||
![]() |
ce7aa5c72d | ||
![]() |
8ea9bdcf9a | ||
![]() |
10a3b1e0f9 | ||
![]() |
352377887c | ||
![]() |
0edc4c0db6 | ||
![]() |
21f73fb56f | ||
![]() |
4d36fc3c61 | ||
![]() |
d9b02bd95b | ||
![]() |
957b4f5266 | ||
![]() |
64ee4c518f | ||
![]() |
f6d6c74bc6 | ||
![]() |
20c2db4b35 | ||
![]() |
305ded4986 | ||
![]() |
823283183e | ||
![]() |
721e37882d | ||
![]() |
1a7b8264a1 | ||
![]() |
530caa7ea6 |
45 changed files with 904 additions and 886 deletions
16
.gitattributes
vendored
Normal file
16
.gitattributes
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
# Set the default behavior, in case people don't have core.autocrlf set.
|
||||
* text=auto
|
||||
|
||||
# Explicitly declare text files you want to always be normalized and converted
|
||||
# to native line endings on checkout.
|
||||
*.c text
|
||||
*.h text
|
||||
*.php text
|
||||
|
||||
# Declare files that will always have LF line endings on checkout.
|
||||
*.sln text eol=lf
|
||||
*.php text eol.lf
|
||||
|
||||
# Denote all files that are truly binary and should not be modified.
|
||||
*.png binary
|
||||
*.jpg binary
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -6,6 +6,3 @@ php-docs
|
|||
phpdoc
|
||||
html
|
||||
/cache
|
||||
contentnation.ini
|
||||
*.pem*
|
||||
composer.phar
|
||||
|
|
15
config.ini
15
config.ini
|
@ -1,7 +1,5 @@
|
|||
[generic]
|
||||
protocol = 'https'
|
||||
externaldomain = 'federator.your.fqdn'
|
||||
sourcedomain = 'your.fqdn'
|
||||
externaldomain = 'contentnation.net'
|
||||
|
||||
[database]
|
||||
host = '127.0.0.1'
|
||||
|
@ -10,17 +8,18 @@ password = '*change*me*'
|
|||
database = 'federator'
|
||||
|
||||
[templates]
|
||||
path = '../templates/federator/'
|
||||
compiledir = '../cache'
|
||||
path = 'templates/federator/'
|
||||
compiledir = 'cache'
|
||||
|
||||
[plugins]
|
||||
rediscache = 'rediscache.php'
|
||||
dummy = 'dummyconnector.php'
|
||||
# dummy = 'dummyconnector.php'
|
||||
contentnation = 'contentnation.php'
|
||||
|
||||
[maintenance]
|
||||
username = 'federatoradmin'
|
||||
password = '*change*me*as*well'
|
||||
|
||||
[keys]
|
||||
federatorPrivateKeyPath = 'federator.pem'
|
||||
federatorPublicKeyPath = 'federator.pem.pub'
|
||||
federatorPrivateKeyPath = 'federator.key'
|
||||
federatorPublicKeyPath = 'federator.pub'
|
10
contentnation.ini
Normal file
10
contentnation.ini
Normal file
|
@ -0,0 +1,10 @@
|
|||
[contentnation]
|
||||
service-uri = http://local.contentnation.net
|
||||
|
||||
[userdata]
|
||||
path = '/home/net/contentnation/userdata/htdocs/' // need to download local copy of image and put img-path here
|
||||
url = 'https://userdata.contentnation.net'
|
||||
|
||||
[keys]
|
||||
headerSenderName = 'contentnation'
|
||||
publicKeyPath = 'contentnation.pub'
|
|
@ -14,6 +14,7 @@ date_default_timezone_set("Europe/Berlin");
|
|||
spl_autoload_register(static function (string $className) {
|
||||
include '../php/' . str_replace("\\", "/", strtolower($className)) . '.php';
|
||||
});
|
||||
define('PROJECT_ROOT', dirname(__DIR__, 1));
|
||||
|
||||
/// main instance
|
||||
$contentnation = new \Federator\Api();
|
||||
|
|
113
htdocs/index.html
Normal file
113
htdocs/index.html
Normal file
|
@ -0,0 +1,113 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>API Request UI</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
</head>
|
||||
|
||||
<body class="flex justify-center items-center min-h-screen bg-gray-100">
|
||||
<div class="w-full max-w-3xl bg-white shadow-lg rounded-lg p-6 m-6">
|
||||
<h2 class="text-2xl font-semibold mb-4 text-center">API Request UI</h2>
|
||||
|
||||
<div id="request-container">
|
||||
<!-- Request Form Template -->
|
||||
<div class="request-box border p-4 rounded-lg mb-4 bg-gray-50 overflow-y-auto">
|
||||
<label class="block font-medium">API target link</label>
|
||||
<input type="text" class="target-link-input w-full p-2 border rounded-md mb-2"
|
||||
placeholder="Enter target link" value="users/grumpydevelop/outbox?page=0">
|
||||
|
||||
<label class="block font-medium">Request type</label>
|
||||
<input type="text" class="request-type-input w-full p-2 border rounded-md mb-2"
|
||||
placeholder="POST or GET" value="GET">
|
||||
|
||||
<label class="block font-medium">X-Session:</label>
|
||||
<input type="text" class="session-input w-full p-2 border rounded-md mb-2"
|
||||
placeholder="Enter X-Session token" value="">
|
||||
|
||||
<label class="block font-medium">X-Profile:</label>
|
||||
<input type="text" class="profile-input w-full p-2 border rounded-md mb-2" placeholder="Enter X-Profile"
|
||||
value="">
|
||||
|
||||
<div class="buttonContainer">
|
||||
<button class="send-btn bg-blue-500 text-white px-4 py-2 rounded-md w-full hover:bg-blue-600">
|
||||
Send Request
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p class="mt-2 text-sm text-gray-700">Response:</p>
|
||||
<p class="response mt-2 text-sm text-gray-700 font-mono whitespace-pre-wrap">Waiting for response</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button id="add-request" class="mt-4 bg-green-500 text-white px-4 py-2 rounded-md w-full hover:bg-green-600">
|
||||
Add Another Request
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function sendRequest(button) {
|
||||
const container = button.parentElement.parentElement;
|
||||
const targetLink = container.querySelector(".target-link-input").value;
|
||||
const requestType = container.querySelector(".request-type-input").value;
|
||||
const session = container.querySelector(".session-input").value;
|
||||
const profile = container.querySelector(".profile-input").value;
|
||||
const responseField = container.querySelector(".response");
|
||||
button.parentElement.style.cursor = "not-allowed";
|
||||
button.style.pointerEvents = "none";
|
||||
button.textContent = "Sending...";
|
||||
responseField.textContent = "Waiting for response";
|
||||
|
||||
const headers = {
|
||||
...(session ? { "X-Session": session } : {}),
|
||||
...(profile ? { "X-Profile": profile } : {}),
|
||||
"HTTP_HOST": "localhost",
|
||||
};
|
||||
|
||||
fetch("http://localhost/" + targetLink, {
|
||||
method: requestType,
|
||||
headers
|
||||
})
|
||||
.then(response => response.text())
|
||||
.then(data => {
|
||||
responseField.textContent = data;
|
||||
button.parentElement.style.cursor = "";
|
||||
button.style.pointerEvents = "";
|
||||
button.textContent = "Send Request";
|
||||
})
|
||||
.catch(error => {
|
||||
responseField.textContent = "Error: " + error;
|
||||
button.parentElement.style.cursor = "";
|
||||
button.style.pointerEvents = "";
|
||||
button.textContent = "Send Request";
|
||||
});
|
||||
}
|
||||
|
||||
document.querySelectorAll(".send-btn").forEach(btn => {
|
||||
btn.addEventListener("click", function () {
|
||||
sendRequest(this);
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById("add-request").addEventListener("click", function () {
|
||||
const container = document.getElementById("request-container");
|
||||
const requestBox = container.firstElementChild.cloneNode(true);
|
||||
|
||||
requestBox.querySelector(".target-link-input").value = "users/grumpydevelop@contentnation.net/outbox?page=0";
|
||||
requestBox.querySelector(".request-type-input").value = "GET";
|
||||
requestBox.querySelector(".session-input").value = "";
|
||||
requestBox.querySelector(".profile-input").value = "";
|
||||
requestBox.querySelector(".response").textContent = "Waiting for response";
|
||||
|
||||
requestBox.querySelector(".send-btn").addEventListener("click", function () {
|
||||
sendRequest(this);
|
||||
});
|
||||
|
||||
container.appendChild(requestBox);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -46,7 +46,7 @@ class Api extends Main
|
|||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->contentType = 'application/json';
|
||||
$this->contentType = "application/json";
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
|
@ -63,7 +63,7 @@ class Api extends Main
|
|||
while ($this->path[0] === '/') {
|
||||
$this->path = substr($this->path, 1);
|
||||
}
|
||||
$this->paths = explode('/', $this->path);
|
||||
$this->paths = explode("/", $this->path);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -74,7 +74,8 @@ class Api extends Main
|
|||
$this->setPath((string) $_REQUEST['_call']);
|
||||
$this->openDatabase();
|
||||
$this->loadPlugins();
|
||||
$retval = '';
|
||||
|
||||
$retval = "";
|
||||
$handler = null;
|
||||
if ($this->connector === null) {
|
||||
http_response_code(500);
|
||||
|
@ -100,7 +101,7 @@ class Api extends Main
|
|||
break;
|
||||
case 'fedusers':
|
||||
$handler = new Api\FedUsers($this);
|
||||
$this->setContentType('application/activity+json');
|
||||
$this->setContentType("application/activity+json");
|
||||
break;
|
||||
case 'v1':
|
||||
switch ($this->paths[1]) {
|
||||
|
@ -110,6 +111,39 @@ class Api extends Main
|
|||
case 'newcontent':
|
||||
$handler = new Api\V1\NewContent($this);
|
||||
break;
|
||||
/* case 'sendFollow': { // hacky implementation for testing purposes
|
||||
$username = $this->paths[2];
|
||||
$domain = $this->config['generic']['externaldomain'];
|
||||
$response = \Federator\DIO\Followers::sendFollowRequest(
|
||||
$this->dbh,
|
||||
$this->connector,
|
||||
$this->cache,
|
||||
$username,
|
||||
"admin@mastodon.local",
|
||||
$domain
|
||||
);
|
||||
header("Content-type: " . $this->contentType);
|
||||
header("Access-Control-Allow-Origin: *");
|
||||
header("Cache-Control: no-cache, no-store, must-revalidate");
|
||||
header("Pragma: no-cache");
|
||||
header("Expires: 0");
|
||||
if (is_string($response)) {
|
||||
$this->setResponseCode(200);
|
||||
$retval = json_encode(array(
|
||||
"status" => "ok",
|
||||
"message" => $response
|
||||
));
|
||||
} else {
|
||||
$this->setResponseCode(500);
|
||||
$retval = json_encode(array(
|
||||
"status" => "error",
|
||||
"message" => "Failed to send follow request"
|
||||
));
|
||||
}
|
||||
http_response_code($this->responseCode);
|
||||
echo $retval;
|
||||
return;
|
||||
} */
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -123,7 +157,7 @@ class Api extends Main
|
|||
} catch (Exceptions\Exception $e) {
|
||||
$this->setResponseCode($e->getRetCode());
|
||||
$retval = json_encode(array(
|
||||
'error' => $e->getMessage()
|
||||
"error" => $e->getMessage()
|
||||
));
|
||||
}
|
||||
} else {
|
||||
|
@ -139,26 +173,26 @@ class Api extends Main
|
|||
}
|
||||
if ($printresponse) {
|
||||
if ($this->redirect !== null) {
|
||||
header('Location: ' . $this->redirect);
|
||||
header("Location: $this->redirect");
|
||||
}
|
||||
if ($this->responseCode != 404) {
|
||||
header('Content-type: ' . $this->contentType);
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header("Content-type: " . $this->contentType);
|
||||
header("Access-Control-Allow-Origin: *");
|
||||
}
|
||||
if ($this->cacheTime == 0) {
|
||||
header('Cache-Control: no-cache, no-store, must-revalidate');
|
||||
header('Pragma: no-cache');
|
||||
header('Expires: 0');
|
||||
header("Cache-Control: no-cache, no-store, must-revalidate");
|
||||
header("Pragma: no-cache");
|
||||
header("Expires: 0");
|
||||
} else {
|
||||
$ts = gmdate('D, d M Y H:i:s', time() + $this->cacheTime) . ' GMT';
|
||||
header('Expires: ' . $ts);
|
||||
header('Pragma: cache');
|
||||
header('Cache-Control: max-age=' . $this->cacheTime);
|
||||
$ts = gmdate("D, d M Y H:i:s", time() + $this->cacheTime) . " GMT";
|
||||
header("Expires: $ts");
|
||||
header("Pragma: cache");
|
||||
header("Cache-Control: max-age=" . $this->cacheTime);
|
||||
}
|
||||
echo $retval;
|
||||
} else {
|
||||
if (!headers_sent()) {
|
||||
header('Content-type: ' . $this->contentType);
|
||||
header("Content-type: " . $this->contentType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -172,7 +206,7 @@ class Api extends Main
|
|||
* @param string $message optional message
|
||||
* @throws Exceptions\PermissionDenied
|
||||
*/
|
||||
public function checkPermission($permission, $exception = '\Exceptions\PermissionDenied', $message = null): void
|
||||
public function checkPermission($permission, $exception = "\Exceptions\PermissionDenied", $message = null): void
|
||||
{
|
||||
// generic check first
|
||||
if ($this->user === false) {
|
||||
|
@ -216,7 +250,7 @@ class Api extends Main
|
|||
$signatureHeader = $headers['Signature'] ?? null;
|
||||
|
||||
if (!isset($signatureHeader)) {
|
||||
throw new Exceptions\PermissionDenied('Missing Signature header');
|
||||
throw new Exceptions\PermissionDenied("Missing Signature header");
|
||||
}
|
||||
|
||||
// Parse Signature header
|
||||
|
@ -226,37 +260,33 @@ class Api extends Main
|
|||
$signature = base64_decode($signatureParts['signature']);
|
||||
$signedHeaders = explode(' ', $signatureParts['headers']);
|
||||
$keyId = $signatureParts['keyId'];
|
||||
$publicKeyPem = false;
|
||||
if ($this->cache !== null) {
|
||||
$publicKeyPem = $this->cache->getPublicKey($keyId);
|
||||
}
|
||||
|
||||
if ($publicKeyPem === false) {
|
||||
$publicKeyPem = $this->cache->getPublicKey($keyId);
|
||||
|
||||
if (!isset($publicKeyPem) || $publicKeyPem === false) {
|
||||
// Fetch public key from `keyId` (usually actor URL + #main-key)
|
||||
[$publicKeyData, $info] = \Federator\Main::getFromRemote($keyId, ['Accept: application/activity+json']);
|
||||
|
||||
if ($info['http_code'] != 200) {
|
||||
throw new Exceptions\PermissionDenied('Failed to fetch public key from keyId: ' . $keyId);
|
||||
throw new Exceptions\PermissionDenied("Failed to fetch public key from keyId: $keyId");
|
||||
}
|
||||
|
||||
$actor = json_decode($publicKeyData, true);
|
||||
|
||||
if (!is_array($actor) || !isset($actor['id'])) {
|
||||
throw new Exceptions\PermissionDenied('Invalid actor data');
|
||||
throw new Exceptions\PermissionDenied("Invalid actor data");
|
||||
}
|
||||
|
||||
$publicKeyPem = $actor['publicKey']['publicKeyPem'] ?? null;
|
||||
|
||||
if (!isset($publicKeyPem) || $publicKeyPem === false) {
|
||||
http_response_code(500);
|
||||
throw new Exceptions\PermissionDenied('Public key couldn\'t be determined');
|
||||
throw new Exceptions\PermissionDenied("Public key couldn't be determined");
|
||||
}
|
||||
|
||||
// Cache the public key for 1 hour
|
||||
if ($this->cache !== null) {
|
||||
$this->cache->savePublicKey($keyId, $publicKeyPem);
|
||||
}
|
||||
}
|
||||
|
||||
// Reconstruct the signed string
|
||||
$signedString = '';
|
||||
|
@ -269,7 +299,7 @@ class Api extends Main
|
|||
$headerValue = $headers[ucwords($header, '-')] ?? '';
|
||||
}
|
||||
|
||||
$signedString .= strtolower($header) . ': ' . $headerValue . "\n";
|
||||
$signedString .= strtolower($header) . ": " . $headerValue . "\n";
|
||||
}
|
||||
|
||||
$signedString = rtrim($signedString);
|
||||
|
@ -282,11 +312,11 @@ class Api extends Main
|
|||
}
|
||||
if ($verified != 1) {
|
||||
http_response_code(500);
|
||||
throw new Exceptions\PermissionDenied('Signature verification failed');
|
||||
throw new Exceptions\PermissionDenied("Signature verification failed");
|
||||
}
|
||||
|
||||
// Signature is valid!
|
||||
return 'Signature verified.';
|
||||
return "Signature verified.";
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -47,7 +47,7 @@ class FedUsers implements APIInterface
|
|||
*/
|
||||
public function exec($paths, $user)
|
||||
{
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$method = $_SERVER["REQUEST_METHOD"];
|
||||
$handler = null;
|
||||
$_username = $paths[1];
|
||||
switch (sizeof($paths)) {
|
||||
|
@ -58,7 +58,7 @@ class FedUsers implements APIInterface
|
|||
} else {
|
||||
switch ($paths[1]) {
|
||||
case 'inbox':
|
||||
$_username = null;
|
||||
$_username = NULL;
|
||||
$handler = new FedUsers\Inbox($this->main);
|
||||
break;
|
||||
default:
|
||||
|
@ -127,7 +127,6 @@ class FedUsers implements APIInterface
|
|||
}
|
||||
$config = $this->main->getConfig();
|
||||
$domain = $config['generic']['externaldomain'];
|
||||
$sourcedomain = $config['generic']['sourcedomain'];
|
||||
$jsonKey = json_encode($user->publicKey);
|
||||
if (!is_string($jsonKey)) {
|
||||
throw new \Federator\Exceptions\FileNotFound();
|
||||
|
@ -138,7 +137,6 @@ class FedUsers implements APIInterface
|
|||
'imageMediaType' => $user->imageMediaType,
|
||||
'imageURL' => $user->imageURL,
|
||||
'fqdn' => $domain,
|
||||
'sourcedomain' => $sourcedomain,
|
||||
'name' => $user->name,
|
||||
'username' => $user->id,
|
||||
'publickey' => trim($jsonKey, '"'),
|
||||
|
@ -149,7 +147,6 @@ class FedUsers implements APIInterface
|
|||
$this->response = $this->main->renderTemplate('user.json', $data);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* set response
|
||||
*
|
||||
|
|
|
@ -59,17 +59,16 @@ class Followers implements \Federator\Api\FedUsers\FedUsersInterface
|
|||
$followerItems = \Federator\DIO\Followers::getFollowersByUser($dbh, $user->id, $connector, $cache);
|
||||
|
||||
$config = $this->main->getConfig();
|
||||
$protocol = $config['generic']['protocol'];
|
||||
$domain = $config['generic']['externaldomain'];
|
||||
$baseUrl = $protocol . '://' . $domain . '/' . $_user . '/followers';
|
||||
$baseUrl = 'https://' . $domain . '/' . $_user . '/followers';
|
||||
|
||||
$pageSize = 10;
|
||||
$page = $this->main->extractFromURI('page', '');
|
||||
$page = $this->main->extractFromURI("page", "");
|
||||
$id = $baseUrl;
|
||||
$items = [];
|
||||
$totalItems = count($followerItems);
|
||||
|
||||
if ($page !== '') {
|
||||
if ($page !== "") {
|
||||
$pageNum = max(0, (int) $page);
|
||||
$offset = (int)($pageNum * $pageSize);
|
||||
$pagedItems = array_slice($followerItems, $offset, $pageSize);
|
||||
|
@ -88,11 +87,11 @@ class Followers implements \Federator\Api\FedUsers\FedUsersInterface
|
|||
// Pagination navigation
|
||||
$lastPage = max(0, ceil($totalItems / $pageSize) - 1);
|
||||
|
||||
if ($page === '' || $followers->getCount() == 0) {
|
||||
if ($page === "" || $followers->getCount() == 0) {
|
||||
$followers->setFirst($baseUrl . '?page=0');
|
||||
$followers->setLast($baseUrl . '?page=' . $lastPage);
|
||||
}
|
||||
if ($page !== '') {
|
||||
if ($page !== "") {
|
||||
$pageNum = max(0, (int) $page);
|
||||
if ($pageNum < $lastPage) {
|
||||
$followers->setNext($baseUrl . '?page=' . ($pageNum + 1));
|
||||
|
|
|
@ -43,6 +43,7 @@ class Following implements \Federator\Api\FedUsers\FedUsersInterface
|
|||
$dbh = $this->main->getDatabase();
|
||||
$cache = $this->main->getCache();
|
||||
$connector = $this->main->getConnector();
|
||||
|
||||
// get user
|
||||
$user = \Federator\DIO\User::getUserByName(
|
||||
$dbh,
|
||||
|
@ -55,20 +56,19 @@ class Following implements \Federator\Api\FedUsers\FedUsersInterface
|
|||
}
|
||||
|
||||
$following = new \Federator\Data\ActivityPub\Common\Following();
|
||||
$followingItems = \Federator\DIO\Followers::getFollowingByUser($dbh, $user->id, $connector, $cache);
|
||||
$followingItems = \Federator\DIO\Followers::getFollowingForUser($dbh, $user->id, $connector, $cache);
|
||||
|
||||
$config = $this->main->getConfig();
|
||||
$protocol = $config['generic']['protocol'];
|
||||
$domain = $config['generic']['externaldomain'];
|
||||
$baseUrl = $protocol . '://' . $domain . '/users/' . $_user . '/following';
|
||||
$baseUrl = 'https://' . $domain . '/users/' . $_user . '/following';
|
||||
|
||||
$pageSize = 10;
|
||||
$page = $this->main->extractFromURI('page', '');
|
||||
$page = $this->main->extractFromURI("page", "");
|
||||
$id = $baseUrl;
|
||||
$items = [];
|
||||
$totalItems = count($followingItems);
|
||||
|
||||
if ($page !== '') {
|
||||
if ($page !== "") {
|
||||
$pageNum = max(0, (int) $page);
|
||||
$offset = (int) ($pageNum * $pageSize);
|
||||
$pagedItems = array_slice($followingItems, $offset, $pageSize);
|
||||
|
@ -87,11 +87,11 @@ class Following implements \Federator\Api\FedUsers\FedUsersInterface
|
|||
// Pagination navigation
|
||||
$lastPage = max(0, ceil($totalItems / $pageSize) - 1);
|
||||
|
||||
if ($page === '' || $following->getCount() == 0) {
|
||||
if ($page === "" || $following->getCount() == 0) {
|
||||
$following->setFirst($baseUrl . '?page=0');
|
||||
$following->setLast($baseUrl . '?page=' . $lastPage);
|
||||
}
|
||||
if ($page !== '') {
|
||||
if ($page !== "") {
|
||||
$pageNum = max(0, (int) $page);
|
||||
if ($pageNum < $lastPage) {
|
||||
$following->setNext($baseUrl . '?page=' . ($pageNum + 1));
|
||||
|
|
|
@ -54,8 +54,7 @@ class Inbox implements \Federator\Api\FedUsers\FedUsersInterface
|
|||
try {
|
||||
$this->main->checkSignature($allHeaders);
|
||||
} catch (\Federator\Exceptions\PermissionDenied $e) {
|
||||
error_log("signature check failed");
|
||||
throw new \Federator\Exceptions\Unauthorized('Inbox::post Signature check failed: ' . $e->getMessage());
|
||||
throw new \Federator\Exceptions\Unauthorized("Inbox::post Signature check failed: " . $e->getMessage());
|
||||
}
|
||||
|
||||
$activity = is_string($_rawInput) ? json_decode($_rawInput, true) : null;
|
||||
|
@ -65,18 +64,19 @@ class Inbox implements \Federator\Api\FedUsers\FedUsersInterface
|
|||
$connector = $this->main->getConnector();
|
||||
|
||||
$config = $this->main->getConfig();
|
||||
|
||||
if (!is_array($activity)) {
|
||||
throw new \Federator\Exceptions\ServerError('Inbox::post Input wasn\'t of type array');
|
||||
throw new \Federator\Exceptions\ServerError("Inbox::post Input wasn't of type array");
|
||||
}
|
||||
|
||||
$inboxActivity = \Federator\Data\ActivityPub\Factory::newActivityFromJson($activity);
|
||||
|
||||
if ($inboxActivity === false) {
|
||||
throw new \Federator\Exceptions\ServerError('Inbox::post couldn\'t create inboxActivity');
|
||||
throw new \Federator\Exceptions\ServerError("Inbox::post couldn't create inboxActivity");
|
||||
}
|
||||
$actor = $inboxActivity->getAActor(); // url of the sender https://contentnation.net/username
|
||||
$username = basename((string) (parse_url($actor, PHP_URL_PATH) ?? ''));
|
||||
$domain = parse_url($actor, PHP_URL_HOST);
|
||||
$user = $inboxActivity->getAActor(); // url of the sender https://contentnation.net/username
|
||||
$username = basename((string) (parse_url($user, PHP_URL_PATH) ?? ''));
|
||||
$domain = parse_url($user, PHP_URL_HOST);
|
||||
$userId = $username . '@' . $domain;
|
||||
$user = \Federator\DIO\FedUser::getUserByName(
|
||||
$dbh,
|
||||
|
@ -84,8 +84,8 @@ class Inbox implements \Federator\Api\FedUsers\FedUsersInterface
|
|||
$cache
|
||||
);
|
||||
if ($user === null || $user->id === null) {
|
||||
error_log('Inbox::post couldn\'t find user: ' . $userId);
|
||||
throw new \Federator\Exceptions\ServerError('Inbox::post couldn\'t find user: ' . $userId);
|
||||
error_log("Inbox::post couldn't find user: $userId");
|
||||
throw new \Federator\Exceptions\ServerError("Inbox::post couldn't find user: $userId");
|
||||
}
|
||||
|
||||
$users = [];
|
||||
|
@ -132,56 +132,29 @@ class Inbox implements \Federator\Api\FedUsers\FedUsersInterface
|
|||
}
|
||||
$ourDomain = $config['generic']['externaldomain'];
|
||||
|
||||
$finalReceivers = [];
|
||||
foreach ($receivers as $receiver) {
|
||||
if ($receiver === '' || !is_string($receiver)) {
|
||||
continue;
|
||||
}
|
||||
if (!str_contains($receiver, $ourDomain) && $receiver !== $_user) {
|
||||
continue;
|
||||
}
|
||||
// check if receiver is an actor url from our domain
|
||||
if ($receiver !== $_user) {
|
||||
$receiverName = basename((string) (parse_url($receiver, PHP_URL_PATH) ?? ''));
|
||||
$ourDomain = parse_url($receiver, PHP_URL_HOST);
|
||||
if ($receiverName === null || $ourDomain === null) {
|
||||
error_log('Inbox::post no receiverName or domain found for receiver: ' . $receiver);
|
||||
continue;
|
||||
}
|
||||
if ($receiverName[0] === '@') {
|
||||
$receiverName = substr($receiverName, 1);
|
||||
}
|
||||
$receiver = $receiverName;
|
||||
}
|
||||
$finalReceivers[] = $receiver;
|
||||
}
|
||||
$finalReceivers = array_unique($finalReceivers); // remove duplicates
|
||||
foreach ($finalReceivers as $receiver) {
|
||||
|
||||
if (str_ends_with($receiver, '/followers')) {
|
||||
$actor = $inboxActivity->getAActor();
|
||||
if ($actor === null || !is_string($actor)) {
|
||||
error_log('Inbox::post no actor found');
|
||||
error_log("Inbox::post no actor found");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract username from the actor URL
|
||||
$username = basename((string) (parse_url($actor, PHP_URL_PATH) ?? ''));
|
||||
$domain = parse_url($actor, PHP_URL_HOST);
|
||||
error_log("url $actor to username $username domain $domain");
|
||||
if ($username === null || $domain === null) {
|
||||
error_log('Inbox::post no username or domain found for recipient: ' . $receiver);
|
||||
error_log("Inbox::post no username or domain found for recipient: $receiver");
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
$followers = \Federator\DIO\Followers::getFollowersByFedUser(
|
||||
$dbh,
|
||||
$connector,
|
||||
$cache,
|
||||
$username . '@' . $domain
|
||||
);
|
||||
$followers = \Federator\DIO\Followers::getFollowersByFedUser($dbh, $connector, $cache, $username . '@' . $domain);
|
||||
} catch (\Throwable $e) {
|
||||
error_log('Inbox::post get followers for user: ' . $username . '@' . $domain . '. Exception: '
|
||||
. $e->getMessage());
|
||||
error_log("Inbox::post get followers for user: " . $username . '@' . $domain . ". Exception: " . $e->getMessage());
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -189,6 +162,19 @@ class Inbox implements \Federator\Api\FedUsers\FedUsersInterface
|
|||
$users = array_merge($users, array_column($followers, 'id'));
|
||||
}
|
||||
} else {
|
||||
// check if receiver is an actor url from our domain
|
||||
if (!str_contains($receiver, $ourDomain) && $receiver !== $_user) {
|
||||
continue;
|
||||
}
|
||||
if ($receiver !== $_user) {
|
||||
$receiverName = basename((string) (parse_url($receiver, PHP_URL_PATH) ?? ''));
|
||||
$ourDomain = parse_url($receiver, PHP_URL_HOST);
|
||||
if ($receiverName === null || $ourDomain === null) {
|
||||
error_log("Inbox::post no receiverName or domain found for receiver: " . $receiver);
|
||||
continue;
|
||||
}
|
||||
$receiver = $receiverName;
|
||||
}
|
||||
try {
|
||||
$localUser = \Federator\DIO\User::getUserByName(
|
||||
$dbh,
|
||||
|
@ -197,11 +183,11 @@ class Inbox implements \Federator\Api\FedUsers\FedUsersInterface
|
|||
$cache
|
||||
);
|
||||
} catch (\Throwable $e) {
|
||||
error_log('Inbox::post get user by name: ' . $receiver . '. Exception: ' . $e->getMessage());
|
||||
error_log("Inbox::post get user by name: " . $receiver . ". Exception: " . $e->getMessage());
|
||||
continue;
|
||||
}
|
||||
if ($localUser === null || $localUser->id === null) {
|
||||
error_log('Inbox::post 210 couldn\'t find user: ' . $receiver);
|
||||
error_log("Inbox::post couldn't find user: $receiver");
|
||||
continue;
|
||||
}
|
||||
$users[] = $localUser->id;
|
||||
|
@ -211,27 +197,15 @@ class Inbox implements \Federator\Api\FedUsers\FedUsersInterface
|
|||
$users = array_unique($users); // remove duplicates
|
||||
|
||||
if (empty($users)) { // todo remove after proper implementation, debugging for now
|
||||
$rootDir = '/tmp/';
|
||||
$rootDir = PROJECT_ROOT . '/';
|
||||
// Save the raw input and parsed JSON to a file for inspection
|
||||
file_put_contents(
|
||||
$rootDir . 'logs/inbox.log',
|
||||
date('Y-m-d H:i:s') . ": ==== POST Inbox Activity ====\n"
|
||||
. json_encode($inboxActivity, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) . "\n\n",
|
||||
date('Y-m-d H:i:s') . ": ==== POST Inbox Activity ====\n" . json_encode($inboxActivity, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) . "\n\n",
|
||||
FILE_APPEND
|
||||
);
|
||||
}
|
||||
|
||||
// Set the Redis backend for Resque
|
||||
$rconfig = parse_ini_file($_SERVER['DOCUMENT_ROOT'] . '/../rediscache.ini');
|
||||
$redisUrl = sprintf(
|
||||
'redis://%s:%s@%s:%d?password-encoding=u',
|
||||
urlencode($rconfig['username']),
|
||||
urlencode($rconfig['password']),
|
||||
$rconfig['host'],
|
||||
intval($rconfig['port'], 10)
|
||||
);
|
||||
\Resque::setBackend($redisUrl);
|
||||
|
||||
foreach ($users as $receiver) {
|
||||
if (!isset($receiver)) {
|
||||
continue;
|
||||
|
@ -241,7 +215,7 @@ class Inbox implements \Federator\Api\FedUsers\FedUsersInterface
|
|||
'recipientId' => $receiver,
|
||||
'activity' => $inboxActivity->toObject(),
|
||||
]);
|
||||
error_log('Inbox::post enqueued job for user: ' . $user->id . ' with token: ' . $token);
|
||||
error_log("Inbox::post enqueued job for user: $user->id with token: $token");
|
||||
}
|
||||
if (empty($users)) {
|
||||
$type = strtolower($inboxActivity->getType());
|
||||
|
@ -251,12 +225,211 @@ class Inbox implements \Federator\Api\FedUsers\FedUsersInterface
|
|||
'recipientId' => "",
|
||||
'activity' => $inboxActivity->toObject(),
|
||||
]);
|
||||
error_log('Inbox::post enqueued job for user: ' . $user->id . ' with token: ' . $token);
|
||||
error_log("Inbox::post enqueued job for user: $user->id with token: $token");
|
||||
} else {
|
||||
error_log('Inbox::post no users found for activity, doing nothing: '
|
||||
. json_encode($inboxActivity, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
|
||||
error_log("Inbox::post no users found for activity, doing nothing: " . json_encode($inboxActivity, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
|
||||
}
|
||||
}
|
||||
return 'success';
|
||||
|
||||
$connector->sendActivity($user, $inboxActivity);
|
||||
|
||||
return "success";
|
||||
}
|
||||
|
||||
/**
|
||||
* handle post call for specific user
|
||||
*
|
||||
* @param \mysqli $dbh database handle
|
||||
* @param \Federator\Connector\Connector $connector connector to use
|
||||
* @param \Federator\Cache\Cache|null $cache optional caching service
|
||||
* @param string $_user user that triggered the post
|
||||
* @param string $_recipientId recipient of the post
|
||||
* @param \Federator\Data\ActivityPub\Common\Activity $inboxActivity the activity that we received
|
||||
* @return boolean response
|
||||
*/
|
||||
public static function postForUser($dbh, $connector, $cache, $_user, $_recipientId, $inboxActivity)
|
||||
{
|
||||
if (!isset($_user)) {
|
||||
error_log("Inbox::postForUser no user given");
|
||||
return false;
|
||||
}
|
||||
|
||||
// get sender
|
||||
$user = \Federator\DIO\FedUser::getUserByName(
|
||||
$dbh,
|
||||
$_user,
|
||||
$cache
|
||||
);
|
||||
if ($user === null || $user->id === null) {
|
||||
error_log("Inbox::postForUser couldn't find user: $_user");
|
||||
return false;
|
||||
}
|
||||
|
||||
$type = strtolower($inboxActivity->getType());
|
||||
|
||||
if ($_recipientId === '') {
|
||||
if ($type === 'undo' || $type === 'delete') {
|
||||
switch ($type) {
|
||||
case 'delete':
|
||||
// Delete Note/Post
|
||||
$object = $inboxActivity->getObject();
|
||||
if (is_string($object)) {
|
||||
\Federator\DIO\Posts::deletePost($dbh, $object);
|
||||
} elseif (is_object($object)) {
|
||||
$objectId = $object->getID();
|
||||
\Federator\DIO\Posts::deletePost($dbh, $objectId);
|
||||
} else {
|
||||
error_log("Inbox::postForUser Error in Delete Post for user $user->id, object is not a string or object");
|
||||
error_log(" object of type " . gettype($object));
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'undo':
|
||||
$object = $inboxActivity->getObject();
|
||||
if (is_object($object)) {
|
||||
switch (strtolower($object->getType())) {
|
||||
case 'like':
|
||||
case 'dislike':
|
||||
// Undo Like/Dislike (remove like/dislike)
|
||||
$targetId = $object->getID();
|
||||
// \Federator\DIO\Votes::removeVote($dbh, $user->id, $targetId, 'dislike');
|
||||
\Federator\DIO\Posts::deletePost($dbh, $targetId);
|
||||
break;
|
||||
case 'note':
|
||||
case 'article':
|
||||
// Undo Note (remove note)
|
||||
$noteId = $object->getID();
|
||||
\Federator\DIO\Posts::deletePost($dbh, $noteId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
error_log("Inbox::postForUser Unhandled activity type $type for user $user->id");
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
$atPos = strpos($_recipientId, '@');
|
||||
if ($atPos !== false) {
|
||||
$_recipientId = substr($_recipientId, 0, $atPos);
|
||||
}
|
||||
|
||||
// get recipient
|
||||
$recipient = \Federator\DIO\User::getUserByName(
|
||||
$dbh,
|
||||
$_recipientId,
|
||||
$connector,
|
||||
$cache
|
||||
);
|
||||
if ($recipient === null || $recipient->id === null) {
|
||||
error_log("Inbox::postForUser couldn't find recipient: $_recipientId");
|
||||
return false;
|
||||
}
|
||||
|
||||
$rootDir = PROJECT_ROOT . '/';
|
||||
// Save the raw input and parsed JSON to a file for inspection
|
||||
file_put_contents(
|
||||
$rootDir . 'logs/inbox_' . $recipient->id . '.log',
|
||||
date('Y-m-d H:i:s') . ": ==== POST " . $recipient->id . " Inbox Activity ====\n" . json_encode($inboxActivity, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) . "\n\n",
|
||||
FILE_APPEND
|
||||
);
|
||||
|
||||
switch ($type) {
|
||||
case 'follow':
|
||||
$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");
|
||||
}
|
||||
break;
|
||||
|
||||
case 'delete':
|
||||
// Delete Note/Post
|
||||
$object = $inboxActivity->getObject();
|
||||
if (is_string($object)) {
|
||||
\Federator\DIO\Posts::deletePost($dbh, $object);
|
||||
} elseif (is_object($object)) {
|
||||
$objectId = $object->getID();
|
||||
\Federator\DIO\Posts::deletePost($dbh, $objectId);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'undo':
|
||||
$object = $inboxActivity->getObject();
|
||||
if (is_object($object)) {
|
||||
switch (strtolower($object->getType())) {
|
||||
case 'follow':
|
||||
$success = false;
|
||||
if ($object instanceof \Federator\Data\ActivityPub\Common\Activity) {
|
||||
$actor = $object->getAActor();
|
||||
if ($actor !== '') {
|
||||
$success = \Federator\DIO\Followers::removeFollow($dbh, $user->id, $recipient->id);
|
||||
}
|
||||
}
|
||||
if ($success === false) {
|
||||
error_log("Inbox::postForUser Failed to remove follower for user $user->id");
|
||||
}
|
||||
break;
|
||||
case 'like':
|
||||
case 'dislike':
|
||||
// Undo Like/Dislike (remove like/dislike)
|
||||
$targetId = $object->getID();
|
||||
// \Federator\DIO\Votes::removeVote($dbh, $user->id, $targetId, 'dislike');
|
||||
\Federator\DIO\Posts::deletePost($dbh, $targetId);
|
||||
break;
|
||||
case 'note':
|
||||
// Undo Note (remove note)
|
||||
$noteId = $object->getID();
|
||||
\Federator\DIO\Posts::deletePost($dbh, $noteId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'like':
|
||||
case 'dislike':
|
||||
// Add Like/Dislike
|
||||
$targetId = $inboxActivity->getObject();
|
||||
if (is_string($targetId)) {
|
||||
// \Federator\DIO\Votes::addVote($dbh, $user->id, $targetId, 'dislike');
|
||||
\Federator\DIO\Posts::savePost($dbh, $user->id, $inboxActivity);
|
||||
} else {
|
||||
error_log("Inbox::postForUser Error in Add Like/Dislike for user $user->id, targetId is not a string");
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'create':
|
||||
case 'update':
|
||||
$object = $inboxActivity->getObject();
|
||||
if (is_object($object)) {
|
||||
switch (strtolower($object->getType())) {
|
||||
case 'note':
|
||||
\Federator\DIO\Posts::savePost($dbh, $user->id, $inboxActivity);
|
||||
|
||||
break;
|
||||
case 'article':
|
||||
\Federator\DIO\Posts::savePost($dbh, $user->id, $inboxActivity);
|
||||
|
||||
break;
|
||||
default:
|
||||
\Federator\DIO\Posts::savePost($dbh, $user->id, $inboxActivity);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
error_log("Inbox::postForUser Unhandled activity type $type for user $user->id");
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ class Outbox implements \Federator\Api\FedUsers\FedUsersInterface
|
|||
$outbox = new \Federator\Data\ActivityPub\Common\Outbox();
|
||||
$min = intval($this->main->extractFromURI('min', '0'), 10);
|
||||
$max = intval($this->main->extractFromURI('max', '0'), 10);
|
||||
$page = $this->main->extractFromURI('page', '');
|
||||
$page = $this->main->extractFromURI("page", "");
|
||||
if ($page !== "") {
|
||||
$items = \Federator\DIO\Posts::getPostsByUser($dbh, $user->id, $connector, $cache, $min, $max, 20);
|
||||
$outbox->setItems($items);
|
||||
|
@ -68,9 +68,8 @@ class Outbox implements \Federator\Api\FedUsers\FedUsersInterface
|
|||
$items = [];
|
||||
}
|
||||
$config = $this->main->getConfig();
|
||||
$protocol = $config['generic']['protocol'];
|
||||
$domain = $config['generic']['externaldomain'];
|
||||
$id = $protocol . '://' . $domain . '/' . $_user . '/outbox';
|
||||
$id = 'https://' . $domain . '/' . $_user . '/outbox';
|
||||
$outbox->setPartOf($id);
|
||||
$outbox->setID($id);
|
||||
if ($page === '') {
|
||||
|
|
|
@ -20,11 +20,7 @@ class Dummy implements \Federator\Api\APIInterface
|
|||
*/
|
||||
private $main;
|
||||
|
||||
/**
|
||||
* internal message to output
|
||||
*
|
||||
* @var Array<string, mixed> $message
|
||||
*/
|
||||
/** @var array<string, string> $message internal message to output */
|
||||
private $message = [];
|
||||
|
||||
/**
|
||||
|
@ -55,8 +51,11 @@ class Dummy implements \Federator\Api\APIInterface
|
|||
case 'GET':
|
||||
switch (sizeof($paths)) {
|
||||
case 3:
|
||||
if ($paths[2] === 'moo') {
|
||||
switch ($paths[2]) {
|
||||
case 'moo':
|
||||
return $this->getDummy();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -64,13 +63,15 @@ class Dummy implements \Federator\Api\APIInterface
|
|||
case 'POST':
|
||||
switch (sizeof($paths)) {
|
||||
case 3:
|
||||
if ($paths[2] === 'moo') {
|
||||
switch ($paths[2]) {
|
||||
case 'moo':
|
||||
return $this->postDummy();
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->main->setResponseCode(404);
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -47,11 +47,10 @@ class NewContent implements \Federator\Api\APIInterface
|
|||
*/
|
||||
public function exec($paths, $user)
|
||||
{
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$method = $_SERVER["REQUEST_METHOD"];
|
||||
$_username = $paths[2];
|
||||
if ($method === 'GET') { // unsupported
|
||||
/// TODO: throw unsupported method exception
|
||||
throw new \Federator\Exceptions\InvalidArgument('GET not supported');
|
||||
throw new \Federator\Exceptions\InvalidArgument("GET not supported");
|
||||
}
|
||||
switch (sizeof($paths)) {
|
||||
case 3:
|
||||
|
@ -68,6 +67,7 @@ class NewContent implements \Federator\Api\APIInterface
|
|||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* handle post call
|
||||
*
|
||||
|
@ -81,7 +81,7 @@ class NewContent implements \Federator\Api\APIInterface
|
|||
try {
|
||||
$this->main->checkSignature($allHeaders);
|
||||
} catch (\Federator\Exceptions\PermissionDenied $e) {
|
||||
error_log('NewContent::post Signature check failed: ' . $e->getMessage());
|
||||
error_log("NewContent::post Signature check failed: " . $e->getMessage());
|
||||
http_response_code(401);
|
||||
return false;
|
||||
}
|
||||
|
@ -96,19 +96,19 @@ class NewContent implements \Federator\Api\APIInterface
|
|||
$config = $this->main->getConfig();
|
||||
$domain = $config['generic']['externaldomain'];
|
||||
if (!is_array($input)) {
|
||||
error_log('NewContent::post Input wasn\'t of type array');
|
||||
error_log("NewContent::post Input wasn't of type array");
|
||||
return false;
|
||||
}
|
||||
|
||||
$articleId = '';
|
||||
$articleId = "";
|
||||
if (isset($allHeaders['X-Sender'])) {
|
||||
$newActivity = $connector->jsonToActivity($input, $articleId);
|
||||
} else {
|
||||
error_log('NewContent::post No X-Sender header found');
|
||||
error_log("NewContent::post No X-Sender header found");
|
||||
return false;
|
||||
}
|
||||
if ($newActivity === false) {
|
||||
error_log('NewContent::post couldn\'t create newActivity');
|
||||
error_log("NewContent::post couldn't create newActivity");
|
||||
return false;
|
||||
}
|
||||
if (!isset($_user)) {
|
||||
|
@ -147,15 +147,15 @@ class NewContent implements \Federator\Api\APIInterface
|
|||
}
|
||||
|
||||
if (str_ends_with($receiver, '/followers')) {
|
||||
|
||||
if ($posterName === null) {
|
||||
error_log('NewContent::post no username found');
|
||||
error_log("NewContent::post no username found");
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
$followers = \Federator\DIO\Followers::getFollowersByUser($dbh, $posterName, $connector, $cache);
|
||||
} catch (\Throwable $e) {
|
||||
error_log('NewContent::post get followers for user: ' . $posterName . '. Exception: '
|
||||
. $e->getMessage());
|
||||
error_log("NewContent::post get followers for user: " . $posterName . ". Exception: " . $e->getMessage());
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -173,7 +173,7 @@ class NewContent implements \Federator\Api\APIInterface
|
|||
if ($receiver === $posterName) {
|
||||
continue;
|
||||
}
|
||||
error_log('NewContent::post no receiverName or domain found for receiver: ' . $receiver);
|
||||
error_log("NewContent::post no receiverName or domain found for receiver: " . $receiver);
|
||||
continue;
|
||||
}
|
||||
$receiver = $receiverName . '@' . $domain;
|
||||
|
@ -184,11 +184,11 @@ class NewContent implements \Federator\Api\APIInterface
|
|||
$cache
|
||||
);
|
||||
} catch (\Throwable $e) {
|
||||
error_log('NewContent::post get user by name: ' . $receiver . '. Exception: ' . $e->getMessage());
|
||||
error_log("NewContent::post get user by name: " . $receiver . ". Exception: " . $e->getMessage());
|
||||
continue;
|
||||
}
|
||||
if ($user === null || $user->id === null) {
|
||||
error_log('NewContent::post couldn\'t find user: ' . $receiver);
|
||||
error_log("NewContent::post couldn't find user: $receiver");
|
||||
continue;
|
||||
}
|
||||
$users[] = $user->id;
|
||||
|
@ -202,8 +202,7 @@ class NewContent implements \Federator\Api\APIInterface
|
|||
// Save the raw input and parsed JSON to a file for inspection
|
||||
file_put_contents(
|
||||
$rootDir . 'logs/newContent.log',
|
||||
date('Y-m-d H:i:s') . ": ==== POST NewContent Activity ====\n"
|
||||
. json_encode($newActivity, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) . "\n\n",
|
||||
date('Y-m-d H:i:s') . ": ==== POST NewContent Activity ====\n" . json_encode($newActivity, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) . "\n\n",
|
||||
FILE_APPEND
|
||||
);
|
||||
}
|
||||
|
@ -219,7 +218,7 @@ class NewContent implements \Federator\Api\APIInterface
|
|||
'activity' => $newActivity->toObject(),
|
||||
'articleId' => $articleId,
|
||||
]);
|
||||
error_log('Inbox::post enqueued job for receiver: ' . $receiver . ' with token: ' . $token);
|
||||
error_log("Inbox::post enqueued job for receiver: $receiver with token: $token");
|
||||
}
|
||||
|
||||
return json_encode($newActivity, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
|
||||
|
@ -245,7 +244,7 @@ class NewContent implements \Federator\Api\APIInterface
|
|||
public static function postForUser($dbh, $connector, $cache, $host, $_user, $_recipientId, $newActivity, $articleId)
|
||||
{
|
||||
if (!isset($_user)) {
|
||||
error_log('NewContent::postForUser no user given');
|
||||
error_log("NewContent::postForUser no user given");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -257,7 +256,7 @@ class NewContent implements \Federator\Api\APIInterface
|
|||
$cache
|
||||
);
|
||||
if ($user === null || $user->id === null) {
|
||||
error_log('NewContent::postForUser couldn\'t find user: ' . $_user);
|
||||
error_log("NewContent::postForUser couldn't find user: $_user");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -268,7 +267,7 @@ class NewContent implements \Federator\Api\APIInterface
|
|||
$cache
|
||||
);
|
||||
if ($recipient === null || $recipient->id === null) {
|
||||
error_log('NewContent::postForUser couldn\'t find user: ' . $_recipientId);
|
||||
error_log("NewContent::postForUser couldn't find user: $_recipientId");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -276,8 +275,7 @@ class NewContent implements \Federator\Api\APIInterface
|
|||
// Save the raw input and parsed JSON to a file for inspection
|
||||
file_put_contents(
|
||||
$rootDir . 'logs/newcontent_' . $recipient->id . '.log',
|
||||
date('Y-m-d H:i:s') . ": ==== POST " . $recipient->id . " NewContent Activity ====\n"
|
||||
. json_encode($newActivity, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) . "\n\n",
|
||||
date('Y-m-d H:i:s') . ": ==== POST " . $recipient->id . " NewContent Activity ====\n" . json_encode($newActivity, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) . "\n\n",
|
||||
FILE_APPEND
|
||||
);
|
||||
|
||||
|
@ -288,12 +286,13 @@ class NewContent implements \Federator\Api\APIInterface
|
|||
// $success = false;
|
||||
$actor = $newActivity->getAActor();
|
||||
if ($actor !== '') {
|
||||
// $followerUsername = basename((string) (parse_url($actor, PHP_URL_PATH) ?? ''));
|
||||
// $followerDomain = parse_url($actor, PHP_URL_HOST);
|
||||
$newIdUrl = \Federator\DIO\Followers::generateNewFollowId($dbh, $host);
|
||||
$newActivity->setID($newIdUrl);
|
||||
/* if (is_string($followerDomain)) {
|
||||
$followerId = "{$followerUsername}@{$followerDomain}";
|
||||
$success = \Federator\DIO\Followers::sendFollowRequest($dbh, $connector, $cache, $user->id,
|
||||
$followerId, $followerDomain);
|
||||
$success = \Federator\DIO\Followers::sendFollowRequest($dbh, $connector, $cache, $user->id, $followerId, $followerDomain);
|
||||
} */
|
||||
}
|
||||
/* if ($success === false) {
|
||||
|
@ -324,25 +323,20 @@ class NewContent implements \Federator\Api\APIInterface
|
|||
$followerUsername = basename((string) (parse_url($actor, PHP_URL_PATH) ?? ''));
|
||||
$followerDomain = parse_url($actor, PHP_URL_HOST);
|
||||
if (is_string($followerDomain)) {
|
||||
$followerId = $followerUsername . '@' . $followerDomain;
|
||||
$removedId = \Federator\DIO\Followers::removeFollow(
|
||||
$dbh,
|
||||
$followerId,
|
||||
$user->id
|
||||
);
|
||||
$followerId = "{$followerUsername}@{$followerDomain}";
|
||||
$removedId = \Federator\DIO\Followers::removeFollow($dbh, $followerId, $user->id);
|
||||
if ($removedId !== false) {
|
||||
$object->setID($removedId);
|
||||
$newActivity->setObject($object);
|
||||
$success = true;
|
||||
} else {
|
||||
error_log('NewContent::postForUser Failed to remove follow for user '
|
||||
. $user->id);
|
||||
error_log("NewContent::postForUser Failed to remove follow for user $user->id");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($success === false) {
|
||||
error_log('NewContent::postForUser Failed to remove follower for user ' . $user->id);
|
||||
error_log("NewContent::postForUser Failed to remove follower for user $user->id");
|
||||
}
|
||||
break;
|
||||
case 'like':
|
||||
|
@ -350,10 +344,10 @@ class NewContent implements \Federator\Api\APIInterface
|
|||
if (method_exists($object, 'getObject')) {
|
||||
$targetId = $object->getObject();
|
||||
if (is_string($targetId)) {
|
||||
\Federator\DIO\Votes::removeVote($dbh, $user->id, $targetId);
|
||||
// \Federator\DIO\Votes::removeVote($dbh, $user->id, $targetId);
|
||||
\Federator\DIO\Posts::deletePost($dbh, $targetId);
|
||||
} else {
|
||||
error_log('NewContent::postForUser Error in Undo Like/Dislike for user ' . $user->id
|
||||
. ', targetId is not a string');
|
||||
error_log("NewContent::postForUser Error in Undo Like/Dislike for user $user->id, targetId is not a string");
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -361,6 +355,7 @@ class NewContent implements \Federator\Api\APIInterface
|
|||
// Undo Note (remove note)
|
||||
$noteId = $object->getID();
|
||||
\Federator\DIO\Posts::deletePost($dbh, $noteId);
|
||||
|
||||
break;
|
||||
case 'article':
|
||||
$articleId = $object->getID();
|
||||
|
@ -371,8 +366,7 @@ class NewContent implements \Federator\Api\APIInterface
|
|||
// Undo Article (remove article)
|
||||
$idPart = strrchr($recipient->id, '@');
|
||||
if ($idPart === false) {
|
||||
error_log('NewContent::postForUser Error in Undo Article. ' . $recipient->id
|
||||
. ', recipient ID is not valid');
|
||||
error_log("NewContent::postForUser Error in Undo Article. $recipient->id, recipient ID is not valid");
|
||||
return false;
|
||||
} else {
|
||||
$targetUrl = ltrim($idPart, '@');
|
||||
|
@ -381,8 +375,7 @@ class NewContent implements \Federator\Api\APIInterface
|
|||
$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');
|
||||
error_log("NewContent::postForUser Error in Undo Article for recipient $recipient->id, object is not an Article");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -391,8 +384,7 @@ class NewContent implements \Federator\Api\APIInterface
|
|||
} else if (is_string($object)) {
|
||||
\Federator\DIO\Posts::deletePost($dbh, $object);
|
||||
} else {
|
||||
error_log('NewContent::postForUser Error in Undo for recipient ' . $recipient->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;
|
||||
|
||||
|
@ -401,11 +393,10 @@ class NewContent implements \Federator\Api\APIInterface
|
|||
// Add Like/Dislike
|
||||
$targetId = $newActivity->getObject();
|
||||
if (is_string($targetId)) {
|
||||
\Federator\DIO\Votes::addVote($dbh, $user->id, $targetId, $type);
|
||||
// \Federator\DIO\Posts::savePost($dbh, $user->id, $newActivity, $articleId);
|
||||
// \Federator\DIO\Votes::addVote($dbh, $user->id, $targetId, 'like');
|
||||
\Federator\DIO\Posts::savePost($dbh, $user->id, $newActivity, $articleId);
|
||||
} else {
|
||||
error_log('NewContent::postForUser Error in Add Like/Dislike for recipient ' . $recipient->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;
|
||||
|
@ -424,8 +415,7 @@ class NewContent implements \Federator\Api\APIInterface
|
|||
|
||||
$idPart = strrchr($recipient->id, '@');
|
||||
if ($idPart === false) {
|
||||
error_log('NewContent::postForUser Error in Create/Update Article. ' . $recipient->id
|
||||
. ', recipient ID is not valid');
|
||||
error_log("NewContent::postForUser Error in Create/Update Article. $recipient->id, recipient ID is not valid");
|
||||
return false;
|
||||
} else {
|
||||
$targetUrl = ltrim($idPart, '@');
|
||||
|
@ -434,8 +424,7 @@ class NewContent implements \Federator\Api\APIInterface
|
|||
$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');
|
||||
error_log("NewContent::postForUser Error in Create/Update Article for recipient $recipient->id, object is not an Article");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -449,26 +438,117 @@ class NewContent implements \Federator\Api\APIInterface
|
|||
break;
|
||||
|
||||
default:
|
||||
error_log('NewContent::postForUser Unhandled activity type $type for user ' . $user->id);
|
||||
error_log("NewContent::postForUser Unhandled activity type $type for user $user->id");
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
$response = \Federator\DIO\Server::sendActivity($dbh, $host, $user, $recipient, $newActivity);
|
||||
$response = self::sendActivity($dbh, $host, $user, $recipient, $newActivity);
|
||||
} catch (\Exception $e) {
|
||||
error_log('NewContent::postForUser Failed to send activity: ' . $e->getMessage());
|
||||
error_log("NewContent::postForUser Failed to send activity: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
if (empty($response)) {
|
||||
error_log('NewContent::postForUser Sent activity to ' . $recipient->id);
|
||||
error_log("NewContent::postForUser Sent activity to $recipient->id");
|
||||
} else {
|
||||
error_log('NewContent::postForUser Sent activity to ' . $recipient->id . ' with response: '
|
||||
. json_encode($response, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
|
||||
error_log("NewContent::postForUser Sent activity to $recipient->id with response: " . json_encode($response, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* send activity to federated server
|
||||
*
|
||||
* @param \mysqli $dbh database handle
|
||||
* @param string $host host url of our server (e.g. federator)
|
||||
* @param \Federator\Data\User $sender source user
|
||||
* @param \Federator\Data\FedUser $target federated target user
|
||||
* @param \Federator\Data\ActivityPub\Common\Activity $activity activity to send
|
||||
* @return string|true the generated follow ID on success, false on failure
|
||||
*/
|
||||
public static function sendActivity($dbh, $host, $sender, $target, $activity)
|
||||
{
|
||||
if ($dbh === false) {
|
||||
throw new \Federator\Exceptions\ServerError("NewContent::sendActivity Failed to get database handle");
|
||||
}
|
||||
|
||||
$inboxUrl = $target->inboxURL;
|
||||
|
||||
$json = json_encode($activity, JSON_UNESCAPED_SLASHES);
|
||||
|
||||
if ($json === false) {
|
||||
throw new \Exception('Failed to encode JSON: ' . json_last_error_msg());
|
||||
}
|
||||
$digest = 'SHA-256=' . base64_encode(hash('sha256', $json, true));
|
||||
$date = gmdate('D, d M Y H:i:s') . ' GMT';
|
||||
$parsed = parse_url($inboxUrl);
|
||||
if ($parsed === false) {
|
||||
throw new \Exception('Failed to parse URL: ' . $inboxUrl);
|
||||
}
|
||||
|
||||
if (!isset($parsed['host']) || !isset($parsed['path'])) {
|
||||
throw new \Exception('Invalid inbox URL: missing host or path');
|
||||
}
|
||||
$extHost = $parsed['host'];
|
||||
$path = $parsed['path'];
|
||||
|
||||
// Build the signature string
|
||||
$signatureString = "(request-target): post {$path}\n" .
|
||||
"host: {$extHost}\n" .
|
||||
"date: {$date}\n" .
|
||||
"digest: {$digest}";
|
||||
|
||||
// Get rsa private key
|
||||
$privateKey = \Federator\DIO\User::getrsaprivate($dbh, $sender->id); // OR from DB
|
||||
if ($privateKey === false) {
|
||||
throw new \Exception('Failed to get private key');
|
||||
}
|
||||
$pkeyId = openssl_pkey_get_private($privateKey);
|
||||
|
||||
if ($pkeyId === false) {
|
||||
throw new \Exception('Invalid private key');
|
||||
}
|
||||
|
||||
openssl_sign($signatureString, $signature, $pkeyId, OPENSSL_ALGO_SHA256);
|
||||
$signature_b64 = base64_encode($signature);
|
||||
|
||||
// Build keyId (public key ID from your actor object)
|
||||
$keyId = $host . '/' . $sender->id . '#main-key';
|
||||
|
||||
$signatureHeader = 'keyId="' . $keyId . '",algorithm="rsa-sha256",headers="(request-target) host date digest",signature="' . $signature_b64 . '"';
|
||||
|
||||
$ch = curl_init($inboxUrl);
|
||||
if ($ch === false) {
|
||||
throw new \Exception('Failed to initialize cURL');
|
||||
}
|
||||
$headers = [
|
||||
'Host: ' . $extHost,
|
||||
'Date: ' . $date,
|
||||
'Digest: ' . $digest,
|
||||
'Content-Type: application/activity+json',
|
||||
'Signature: ' . $signatureHeader,
|
||||
'Accept: application/activity+json',
|
||||
];
|
||||
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $json);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
$response = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($response === false) {
|
||||
throw new \Exception("Failed to send activity: " . curl_error($ch));
|
||||
} else {
|
||||
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
if ($httpcode != 200 && $httpcode != 202) {
|
||||
throw new \Exception("Unexpected HTTP code $httpcode: $response");
|
||||
}
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* get internal represenation as json string
|
||||
* @return string json string or html
|
||||
|
|
|
@ -61,7 +61,7 @@ class WellKnown implements APIInterface
|
|||
*/
|
||||
public function exec($paths, $user)
|
||||
{
|
||||
$method = $_SERVER['REQUEST_METHOD'];
|
||||
$method = $_SERVER["REQUEST_METHOD"];
|
||||
switch ($method) {
|
||||
case 'GET':
|
||||
switch (sizeof($paths)) {
|
||||
|
|
|
@ -67,6 +67,7 @@ class NodeInfo
|
|||
$template = 'nodeinfo2.0.json';
|
||||
}
|
||||
$stats = \Federator\DIO\Stats::getStats($this->main);
|
||||
echo "fetch usercount via connector\n";
|
||||
$data['usercount'] = $stats->userCount;
|
||||
$data['postcount'] = $stats->postCount;
|
||||
$data['commentcount'] = $stats->commentCount;
|
||||
|
|
|
@ -48,10 +48,10 @@ class WebFinger
|
|||
$matches = [];
|
||||
$config = $this->main->getConfig();
|
||||
$domain = $config['generic']['externaldomain'];
|
||||
$sourcedomain = $config['generic']['sourcedomain'];
|
||||
if (preg_match("/^acct:([^@]+)@(.*)$/", $_resource, $matches) != 1 || ($matches[2] !== $sourcedomain && $matches[2] !== $domain)) {
|
||||
if (preg_match("/^acct:([^@]+)@(.*)$/", $_resource, $matches) != 1 || $matches[2] !== $domain) {
|
||||
throw new \Federator\Exceptions\InvalidArgument();
|
||||
}
|
||||
$domain = $matches[2];
|
||||
$user = \Federator\DIO\User::getUserByName(
|
||||
$this->main->getDatabase(),
|
||||
$matches[1],
|
||||
|
@ -59,12 +59,12 @@ class WebFinger
|
|||
$this->main->getCache()
|
||||
);
|
||||
if ($user->id == 0) {
|
||||
echo "not found";
|
||||
throw new \Federator\Exceptions\FileNotFound();
|
||||
}
|
||||
$data = [
|
||||
'username' => $user->id,
|
||||
'domain' => $domain,
|
||||
'sourcedomain' => $sourcedomain,
|
||||
];
|
||||
$response = $this->main->renderTemplate('webfinger_acct.json', $data);
|
||||
$this->wellKnown->setResponse($response);
|
||||
|
|
4
php/federator/cache/cache.php
vendored
4
php/federator/cache/cache.php
vendored
|
@ -20,7 +20,7 @@ interface Cache extends \Federator\Connector\Connector
|
|||
* @param \Federator\Data\FedUser[]|false $followers user followers
|
||||
* @return void
|
||||
*/
|
||||
public function saveFollowersByUser($user, $followers);
|
||||
public function saveRemoteFollowersOfUser($user, $followers);
|
||||
|
||||
/**
|
||||
* save remote following for user
|
||||
|
@ -29,7 +29,7 @@ interface Cache extends \Federator\Connector\Connector
|
|||
* @param \Federator\Data\FedUser[]|false $following user following
|
||||
* @return void
|
||||
*/
|
||||
public function saveFollowingByUser($user, $following);
|
||||
public function saveRemoteFollowingForUser($user, $following);
|
||||
|
||||
/**
|
||||
* save remote posts by user
|
||||
|
|
|
@ -17,17 +17,19 @@ interface Connector
|
|||
* get followers of given user
|
||||
*
|
||||
* @param string $id user id
|
||||
|
||||
* @return \Federator\Data\FedUser[]|false
|
||||
*/
|
||||
public function getFollowersByUser($id);
|
||||
public function getRemoteFollowersOfUser($id);
|
||||
|
||||
/**
|
||||
* get following of given user
|
||||
*
|
||||
* @param string $id user id
|
||||
|
||||
* @return \Federator\Data\FedUser[]|false
|
||||
*/
|
||||
public function getFollowingByUser($id);
|
||||
public function getRemoteFollowingForUser($id);
|
||||
|
||||
/**
|
||||
* get posts by given user
|
||||
|
@ -36,6 +38,7 @@ interface Connector
|
|||
* @param int $min min value
|
||||
* @param int $max max value
|
||||
* @param int $limit maximum number of results
|
||||
|
||||
* @return \Federator\Data\ActivityPub\Common\Activity[]|false
|
||||
*/
|
||||
public function getRemotePostsByUser($id, $min, $max, $limit);
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace Federator\Data\ActivityPub\Common;
|
|||
|
||||
class Collection extends APObject
|
||||
{
|
||||
protected int $totalItems = -1;
|
||||
protected int $totalItems = 0;
|
||||
private string $first = '';
|
||||
private string $last = '';
|
||||
|
||||
|
@ -28,7 +28,7 @@ class Collection extends APObject
|
|||
{
|
||||
$return = parent::toObject();
|
||||
$return['type'] = 'Collection';
|
||||
if ($this->totalItems >= 0) {
|
||||
if ($this->totalItems > 0) {
|
||||
$return['totalItems'] = $this->totalItems;
|
||||
}
|
||||
if ($this->first !== '') {
|
||||
|
|
|
@ -49,14 +49,15 @@ class Article
|
|||
*/
|
||||
public static function conditionalConvertToNote($article, $targetUrl)
|
||||
{
|
||||
$supportFile = file_get_contents($_SERVER['DOCUMENT_ROOT'] . '../formatsupport.json');
|
||||
$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']) ||
|
||||
if (
|
||||
!isset($supportlist['activitypub']['article']) ||
|
||||
!is_array($supportlist['activitypub']['article']) ||
|
||||
!in_array($targetUrl, $supportlist['activitypub']['article'], true)
|
||||
) {
|
||||
|
|
|
@ -20,15 +20,15 @@ class FedUser
|
|||
* @param string $_user user/profile name
|
||||
* @return void
|
||||
*/
|
||||
protected static function addUserToDB($dbh, $user, $_user)
|
||||
protected static function addLocalUser($dbh, $user, $_user)
|
||||
{
|
||||
// check if it is timed out user
|
||||
$sql = 'select unix_timestamp(`validuntil`) from fedusers where id=?';
|
||||
$stmt = $dbh->prepare($sql);
|
||||
if ($stmt === false) {
|
||||
throw new \Federator\Exceptions\ServerError('FedUser::addLocalUser Failed to prepare statement');
|
||||
throw new \Federator\Exceptions\ServerError("FedUser::addLocalUser Failed to prepare statement");
|
||||
}
|
||||
$stmt->bind_param('s', $_user);
|
||||
$stmt->bind_param("s", $_user);
|
||||
$validuntil = 0;
|
||||
$ret = $stmt->bind_result($validuntil);
|
||||
$stmt->execute();
|
||||
|
@ -42,10 +42,10 @@ class FedUser
|
|||
$sql .= ' values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, now() + interval 1 day)';
|
||||
$stmt = $dbh->prepare($sql);
|
||||
if ($stmt === false) {
|
||||
throw new \Federator\Exceptions\ServerError('FedUser::addLocalUser Failed to prepare create statement');
|
||||
throw new \Federator\Exceptions\ServerError("FedUser::addLocalUser Failed to prepare create statement");
|
||||
}
|
||||
$stmt->bind_param(
|
||||
'ssssssssssss',
|
||||
"ssssssssssss",
|
||||
$_user,
|
||||
$user->actorURL,
|
||||
$user->name,
|
||||
|
@ -66,10 +66,10 @@ class FedUser
|
|||
$sql .= ' where id=?';
|
||||
$stmt = $dbh->prepare($sql);
|
||||
if ($stmt === false) {
|
||||
throw new \Federator\Exceptions\ServerError('FedUser::extendUser Failed to prepare update statement');
|
||||
throw new \Federator\Exceptions\ServerError("FedUser::extendUser Failed to prepare update statement");
|
||||
}
|
||||
$stmt->bind_param(
|
||||
'ssssssssssss',
|
||||
"ssssssssssss",
|
||||
$user->actorURL,
|
||||
$user->name,
|
||||
$user->publicKey,
|
||||
|
@ -106,9 +106,9 @@ class FedUser
|
|||
$sql = 'select id,unix_timestamp(`validuntil`) from fedusers where id=?';
|
||||
$stmt = $dbh->prepare($sql);
|
||||
if ($stmt === false) {
|
||||
throw new \Federator\Exceptions\ServerError('FedUser::extendUser Failed to prepare statement');
|
||||
throw new \Federator\Exceptions\ServerError("FedUser::extendUser Failed to prepare statement");
|
||||
}
|
||||
$stmt->bind_param('s', $_user);
|
||||
$stmt->bind_param("s", $_user);
|
||||
$validuntil = 0;
|
||||
$ret = $stmt->bind_result($user->id, $validuntil);
|
||||
$stmt->execute();
|
||||
|
@ -118,7 +118,7 @@ class FedUser
|
|||
$stmt->close();
|
||||
// if a new user, create own database entry with additionally needed info
|
||||
if ($user->id === null || $validuntil < time()) {
|
||||
self::addUserToDB($dbh, $user, $_user);
|
||||
self::addLocalUser($dbh, $user, $_user);
|
||||
}
|
||||
|
||||
// no further processing for now
|
||||
|
@ -147,14 +147,14 @@ class FedUser
|
|||
return $user;
|
||||
}
|
||||
// check our db
|
||||
$sql = 'select `id`, `url`, `name`, `publickey`, `summary`, `type`, `inboxurl`, `sharedinboxurl`, ';
|
||||
$sql .= '`followersurl`, `followingurl`, `publickeyid`, `outboxurl`';
|
||||
$sql = 'select `id`, `url`, `name`, `publickey`, `summary`, `type`, `inboxurl`, `sharedinboxurl`, `followersurl`,';
|
||||
$sql .= ' `followingurl`, `publickeyid`, `outboxurl`';
|
||||
$sql .= ' from fedusers where `id`=? and `validuntil`>=now()';
|
||||
$stmt = $dbh->prepare($sql);
|
||||
if ($stmt === false) {
|
||||
throw new \Federator\Exceptions\ServerError('FedUser::getUserByName Failed to prepare statement');
|
||||
throw new \Federator\Exceptions\ServerError("FedUser::getUserByName Failed to prepare statement");
|
||||
}
|
||||
$stmt->bind_param('s', $_name);
|
||||
$stmt->bind_param("s", $_name);
|
||||
$user = new \Federator\Data\FedUser();
|
||||
$ret = $stmt->bind_result(
|
||||
$user->id,
|
||||
|
@ -184,13 +184,11 @@ class FedUser
|
|||
$headers = ['Accept: application/activity+json'];
|
||||
[$response, $info] = \Federator\Main::getFromRemote($remoteURL, $headers);
|
||||
if ($info['http_code'] != 200) {
|
||||
throw new \Federator\Exceptions\ServerError('FedUser::getUserByName Failed to fetch webfinger for '
|
||||
. $_name);
|
||||
throw new \Federator\Exceptions\ServerError("FedUser::getUserByName Failed to fetch webfinger for " . $_name);
|
||||
}
|
||||
$r = json_decode($response, true);
|
||||
if ($r === false || $r === null || !is_array($r)) {
|
||||
throw new \Federator\Exceptions\ServerError('FedUser::getUserByName Failed to decode webfinger for '
|
||||
. $_name);
|
||||
throw new \Federator\Exceptions\ServerError("FedUser::getUserByName Failed to decode webfinger for " . $_name);
|
||||
}
|
||||
// get the webwinger user url and fetch the user
|
||||
if (isset($r['links'])) {
|
||||
|
@ -202,23 +200,17 @@ class FedUser
|
|||
}
|
||||
}
|
||||
if (!isset($remoteURL)) {
|
||||
throw new \Federator\Exceptions\ServerError('FedUser::getUserByName Failed to find self link '
|
||||
. 'in webfinger for ' . $_name);
|
||||
}
|
||||
} else {
|
||||
$remoteURL = $_name;
|
||||
throw new \Federator\Exceptions\ServerError("FedUser::getUserByName Failed to find self link in webfinger for " . $_name);
|
||||
}
|
||||
// fetch the user
|
||||
$headers = ['Accept: application/activity+json'];
|
||||
[$response, $info] = \Federator\Main::getFromRemote($remoteURL, $headers);
|
||||
if ($info['http_code'] != 200) {
|
||||
throw new \Federator\Exceptions\ServerError('FedUser::getUserByName Failed to fetch user from '
|
||||
. 'remoteUrl for ' . $_name);
|
||||
throw new \Federator\Exceptions\ServerError("FedUser::getUserByName Failed to fetch user from remoteUrl for " . $_name);
|
||||
}
|
||||
$r = json_decode($response, true);
|
||||
if ($r === false || $r === null || !is_array($r)) {
|
||||
throw new \Federator\Exceptions\ServerError('FedUser::getUserByName Failed to decode user for '
|
||||
. $_name);
|
||||
throw new \Federator\Exceptions\ServerError("FedUser::getUserByName Failed to decode user for " . $_name);
|
||||
}
|
||||
$r['publicKeyId'] = $r['publicKey']['id'];
|
||||
$r['publicKey'] = $r['publicKey']['publicKeyPem'];
|
||||
|
@ -230,240 +222,21 @@ class FedUser
|
|||
$r['actorURL'] = $remoteURL;
|
||||
$data = json_encode($r);
|
||||
if ($data === false) {
|
||||
throw new \Federator\Exceptions\ServerError('FedUser::getUserByName Failed to encode userdata '
|
||||
. $_name);
|
||||
throw new \Federator\Exceptions\ServerError("FedUser::getUserByName Failed to encode userdata " . $_name);
|
||||
}
|
||||
$user = \Federator\Data\FedUser::createFromJson($data);
|
||||
}
|
||||
}
|
||||
|
||||
if ($cache !== null && $user !== false) {
|
||||
if ($user->id !== null && $user->actorURL !== null) {
|
||||
self::addUserToDB($dbh, $user, $_name);
|
||||
self::addLocalUser($dbh, $user, $_name);
|
||||
}
|
||||
$cache->saveRemoteFedUserByName($_name, $user);
|
||||
}
|
||||
if ($user === false) {
|
||||
throw new \Federator\Exceptions\ServerError('FedUser::getUserByName User not found');
|
||||
throw new \Federator\Exceptions\ServerError("FedUser::getUserByName User not found");
|
||||
}
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* handle post call for specific user
|
||||
*
|
||||
* @param \Federator\Main $main main instance
|
||||
* @param \mysqli $dbh database handle
|
||||
* @param \Federator\Connector\Connector $connector connector to use
|
||||
* @param \Federator\Cache\Cache|null $cache optional caching service
|
||||
* @param string $_user user that triggered the post
|
||||
* @param string $_recipientId recipient of the post
|
||||
* @param \Federator\Data\ActivityPub\Common\Activity $inboxActivity the activity that we received
|
||||
* @return boolean response
|
||||
*/
|
||||
public static function inboxForUser($main, $dbh, $connector, $cache, $_user, $_recipientId, $inboxActivity)
|
||||
{
|
||||
if (!isset($_user)) {
|
||||
error_log('Inbox::postForUser no user given');
|
||||
return false;
|
||||
}
|
||||
|
||||
// get sender
|
||||
$user = \Federator\DIO\FedUser::getUserByName(
|
||||
$dbh,
|
||||
$_user,
|
||||
$cache
|
||||
);
|
||||
if ($user === null || $user->id === null) {
|
||||
error_log('Inbox::postForUser couldn\'t find user: ' . $_user);
|
||||
return false;
|
||||
}
|
||||
|
||||
$type = strtolower($inboxActivity->getType());
|
||||
|
||||
if ($_recipientId === '') {
|
||||
if ($type === 'undo' || $type === 'delete') {
|
||||
switch ($type) {
|
||||
case 'delete':
|
||||
// Delete Note/Post
|
||||
$object = $inboxActivity->getObject();
|
||||
if (is_string($object)) {
|
||||
\Federator\DIO\Posts::deletePost($dbh, $object);
|
||||
} elseif (is_object($object)) {
|
||||
$objectId = $object->getID();
|
||||
\Federator\DIO\Posts::deletePost($dbh, $objectId);
|
||||
} else {
|
||||
error_log('Inbox::postForUser Error in Delete Post for user ' . $user->id
|
||||
. ', object is not a string or object');
|
||||
error_log(' object of type ' . gettype($object));
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'undo':
|
||||
$object = $inboxActivity->getObject();
|
||||
if (is_object($object)) {
|
||||
switch (strtolower($object->getType())) {
|
||||
case 'like':
|
||||
case 'dislike':
|
||||
// Undo Like/Dislike (remove like/dislike)
|
||||
$targetId = $object->getID();
|
||||
// \Federator\DIO\Votes::removeVote($dbh, $user->id, $targetId, 'dislike');
|
||||
\Federator\DIO\Posts::deletePost($dbh, $targetId);
|
||||
break;
|
||||
case 'note':
|
||||
case 'article':
|
||||
// Undo Note (remove note)
|
||||
$noteId = $object->getID();
|
||||
\Federator\DIO\Posts::deletePost($dbh, $noteId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
error_log('Inbox::postForUser Unhandled activity type ' . $type . ' for user ' . $user->id);
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
$atPos = strpos($_recipientId, '@');
|
||||
if ($atPos !== false) {
|
||||
$_recipientId = substr($_recipientId, 0, $atPos);
|
||||
}
|
||||
|
||||
// get recipient
|
||||
$recipient = \Federator\DIO\User::getUserByName(
|
||||
$dbh,
|
||||
$_recipientId,
|
||||
$connector,
|
||||
$cache
|
||||
);
|
||||
if ($recipient === null || $recipient->id === null) {
|
||||
error_log('Inbox::postForUser couldn\'t find recipient: ' . $_recipientId);
|
||||
return false;
|
||||
}
|
||||
|
||||
$rootDir = $_SERVER['DOCUMENT_ROOT'] . '../';
|
||||
// Save the raw input and parsed JSON to a file for inspection
|
||||
file_put_contents(
|
||||
$rootDir . 'logs/inbox_' . $recipient->id . '.log',
|
||||
date('Y-m-d H:i:s') . ": ==== POST " . $recipient->id . " Inbox Activity ====\n"
|
||||
. json_encode($inboxActivity, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) . "\n\n",
|
||||
FILE_APPEND
|
||||
);
|
||||
|
||||
switch ($type) {
|
||||
case 'follow':
|
||||
$success = \Federator\DIO\Followers::addExternalFollow(
|
||||
$dbh,
|
||||
$inboxActivity->getID(),
|
||||
$user->id,
|
||||
$recipient->id
|
||||
);
|
||||
|
||||
if ($success === true) {
|
||||
// send accept back
|
||||
$accept = new \Federator\Data\ActivityPub\Common\Accept();
|
||||
$local = $inboxActivity->getObject();
|
||||
if (is_string($local)) {
|
||||
$accept->setAActor($local);
|
||||
$id = bin2hex(openssl_random_pseudo_bytes(4));
|
||||
$accept->setID($local . '#accepts/follows/' . $id);
|
||||
$obj = new \Federator\Data\ActivityPub\Common\Activity($inboxActivity->getType());
|
||||
$config = $main->getConfig();
|
||||
$ourhost = $config['generic']['protocol'] . '://' . $config['generic']['externaldomain'];
|
||||
$obj->setID($ourhost . '/' . $id);
|
||||
$obj->setAActor($inboxActivity->getAActor());
|
||||
$obj->setObject($local);
|
||||
$accept->setObject($obj);
|
||||
// send
|
||||
\Federator\DIO\Server::sendActivity($dbh, $ourhost, $recipient, $user, $accept);
|
||||
}
|
||||
} else {
|
||||
error_log('Inbox::postForUser Failed to add follower for user ' . $user->id);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'delete':
|
||||
// Delete Note/Post
|
||||
$object = $inboxActivity->getObject();
|
||||
if (is_string($object)) {
|
||||
\Federator\DIO\Posts::deletePost($dbh, $object);
|
||||
} elseif (is_object($object)) {
|
||||
$objectId = $object->getID();
|
||||
\Federator\DIO\Posts::deletePost($dbh, $objectId);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'undo':
|
||||
$object = $inboxActivity->getObject();
|
||||
if (is_object($object)) {
|
||||
switch (strtolower($object->getType())) {
|
||||
case 'follow':
|
||||
$success = false;
|
||||
if ($object instanceof \Federator\Data\ActivityPub\Common\Activity) {
|
||||
$actor = $object->getAActor();
|
||||
if ($actor !== '') {
|
||||
$success = \Federator\DIO\Followers::removeFollow($dbh, $user->id, $recipient->id);
|
||||
}
|
||||
}
|
||||
if ($success === false) {
|
||||
error_log('Inbox::postForUser Failed to remove follower for user ' . $user->id);
|
||||
}
|
||||
break;
|
||||
case 'like':
|
||||
case 'dislike':
|
||||
// Undo Like/Dislike (remove like/dislike)
|
||||
$targetId = $object->getID();
|
||||
\Federator\DIO\Votes::removeVote($dbh, $user->id, $targetId);
|
||||
// \Federator\DIO\Posts::deletePost($dbh, $targetId);
|
||||
break;
|
||||
case 'note':
|
||||
// Undo Note (remove note)
|
||||
$noteId = $object->getID();
|
||||
\Federator\DIO\Posts::deletePost($dbh, $noteId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'like':
|
||||
case 'dislike':
|
||||
// Add Like/Dislike
|
||||
$targetId = $inboxActivity->getObject();
|
||||
if (is_string($targetId)) {
|
||||
\Federator\DIO\Votes::addVote($dbh, $user->id, $targetId, $type);
|
||||
} else {
|
||||
error_log('Inbox::postForUser Error in Add Like/Dislike for user ' . $user->id
|
||||
. ', targetId is not a string');
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'create':
|
||||
case 'update':
|
||||
$object = $inboxActivity->getObject();
|
||||
if (is_object($object)) {
|
||||
switch (strtolower($object->getType())) {
|
||||
case 'note':
|
||||
\Federator\DIO\Posts::savePost($dbh, $user->id, $inboxActivity);
|
||||
break;
|
||||
case 'article':
|
||||
\Federator\DIO\Posts::savePost($dbh, $user->id, $inboxActivity);
|
||||
break;
|
||||
default:
|
||||
\Federator\DIO\Posts::savePost($dbh, $user->id, $inboxActivity);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
error_log('Inbox::postForUser Unhandled activity type $type for user ' . $user->id);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ class Followers
|
|||
{
|
||||
// ask cache
|
||||
if ($cache !== null) {
|
||||
$followers = $cache->getFollowersByUser($id);
|
||||
$followers = $cache->getRemoteFollowersOfUser($id);
|
||||
if ($followers !== false) {
|
||||
return $followers;
|
||||
}
|
||||
|
@ -39,9 +39,9 @@ class Followers
|
|||
$sql = 'select source_user from follows where target_user = ?';
|
||||
$stmt = $dbh->prepare($sql);
|
||||
if ($stmt === false) {
|
||||
throw new \Federator\Exceptions\ServerError('Followers::getFollowersByUser Failed to prepare statement');
|
||||
throw new \Federator\Exceptions\ServerError("Followers::getFollowersByUser Failed to prepare statement");
|
||||
}
|
||||
$stmt->bind_param('s', $id);
|
||||
$stmt->bind_param("s", $id);
|
||||
$stmt->execute();
|
||||
$followerIds = [];
|
||||
$stmt->bind_result($sourceUser);
|
||||
|
@ -57,7 +57,7 @@ class Followers
|
|||
$cache,
|
||||
);
|
||||
} catch (\Throwable $e) {
|
||||
error_log('Followers::getFollowersByUser Exception: ' . $e->getMessage());
|
||||
error_log("Followers::getFollowersByUser Exception: " . $e->getMessage());
|
||||
continue; // Skip this user if an exception occurs
|
||||
}
|
||||
if ($user !== false && $user->id !== null) {
|
||||
|
@ -67,14 +67,14 @@ class Followers
|
|||
|
||||
if ($followers === []) {
|
||||
// ask connector for user-id
|
||||
$followers = $connector->getFollowersByUser($id);
|
||||
$followers = $connector->getRemoteFollowersOfUser($id);
|
||||
if ($followers === false) {
|
||||
$followers = [];
|
||||
}
|
||||
}
|
||||
// save followers to cache
|
||||
if ($cache !== null) {
|
||||
$cache->saveFollowersByUser($id, $followers);
|
||||
$cache->saveRemoteFollowersOfUser($id, $followers);
|
||||
}
|
||||
return $followers;
|
||||
}
|
||||
|
@ -91,11 +91,12 @@ class Followers
|
|||
* optional caching service
|
||||
* @return \Federator\Data\FedUser[]
|
||||
*/
|
||||
public static function getFollowingByUser($dbh, $id, $connector, $cache)
|
||||
|
||||
public static function getFollowingForUser($dbh, $id, $connector, $cache)
|
||||
{
|
||||
// ask cache
|
||||
if ($cache !== null) {
|
||||
$following = $cache->getFollowingByUser($id);
|
||||
$following = $cache->getRemoteFollowingForUser($id);
|
||||
if ($following !== false) {
|
||||
return $following;
|
||||
}
|
||||
|
@ -104,9 +105,9 @@ class Followers
|
|||
$sql = 'select target_user from follows where source_user = ?';
|
||||
$stmt = $dbh->prepare($sql);
|
||||
if ($stmt === false) {
|
||||
throw new \Federator\Exceptions\ServerError('Followers::getFollowingForUser Failed to prepare statement');
|
||||
throw new \Federator\Exceptions\ServerError("Followers::getFollowingForUser Failed to prepare statement");
|
||||
}
|
||||
$stmt->bind_param('s', $id);
|
||||
$stmt->bind_param("s", $id);
|
||||
$stmt->execute();
|
||||
$followingIds = [];
|
||||
$stmt->bind_result($sourceUser);
|
||||
|
@ -122,7 +123,7 @@ class Followers
|
|||
$cache,
|
||||
);
|
||||
} catch (\Throwable $e) {
|
||||
error_log('Followers::getFollowingByUser Exception: ' . $e->getMessage());
|
||||
error_log("Followers::getFollowingForUser Exception: " . $e->getMessage());
|
||||
continue; // Skip this user if an exception occurs
|
||||
}
|
||||
if ($user !== false && $user->id !== null) {
|
||||
|
@ -132,14 +133,14 @@ class Followers
|
|||
|
||||
if ($following === []) {
|
||||
// ask connector for user-id
|
||||
$following = $connector->getFollowingByUser($id);
|
||||
$following = $connector->getRemoteFollowingForUser($id);
|
||||
if ($following === false) {
|
||||
$following = [];
|
||||
}
|
||||
}
|
||||
// save posts to DB
|
||||
if ($cache !== null) {
|
||||
$cache->saveFollowingByUser($id, $following);
|
||||
$cache->saveRemoteFollowingForUser($id, $following);
|
||||
}
|
||||
return $following;
|
||||
}
|
||||
|
@ -165,7 +166,7 @@ class Followers
|
|||
$sql = 'select source_user from follows where target_user = ?';
|
||||
$stmt = $dbh->prepare($sql);
|
||||
if ($stmt === false) {
|
||||
throw new \Federator\Exceptions\ServerError('Followers::getFollowersByFedUser Failed to prepare statement');
|
||||
throw new \Federator\Exceptions\ServerError("Followers::getFollowersByFedUser Failed to prepare statement");
|
||||
}
|
||||
$stmt->bind_param("s", $id);
|
||||
$stmt->execute();
|
||||
|
@ -183,7 +184,7 @@ class Followers
|
|||
$cache
|
||||
);
|
||||
} catch (\Throwable $e) {
|
||||
error_log('Followers::getFollowersByFedUser Exception: ' . $e->getMessage());
|
||||
error_log("Followers::getFollowersByFedUser Exception: " . $e->getMessage());
|
||||
continue; // Skip this user if an exception occurs
|
||||
}
|
||||
if ($user !== false && $user->id !== null) {
|
||||
|
@ -207,7 +208,7 @@ class Followers
|
|||
public static function sendFollowRequest($dbh, $connector, $cache, $_user, $_targetUser, $host)
|
||||
{
|
||||
if ($dbh === false) {
|
||||
throw new \Federator\Exceptions\ServerError('Followers::sendFollowRequest Failed to get database handle');
|
||||
throw new \Federator\Exceptions\ServerError("Followers::sendFollowRequest Failed to get database handle");
|
||||
}
|
||||
$user = \Federator\DIO\User::getUserByName(
|
||||
$dbh,
|
||||
|
@ -288,8 +289,7 @@ class Followers
|
|||
// Build keyId (public key ID from your actor object)
|
||||
$keyId = 'https://' . $host . '/' . $user->id . '#main-key';
|
||||
|
||||
$signatureHeader = 'keyId="' . $keyId . '",algorithm="rsa-sha256",headers="(request-target) host date digest"'
|
||||
. ',signature="' . $signature_b64 . '"';
|
||||
$signatureHeader = 'keyId="' . $keyId . '",algorithm="rsa-sha256",headers="(request-target) host date digest",signature="' . $signature_b64 . '"';
|
||||
|
||||
$ch = curl_init($inboxUrl);
|
||||
if ($ch === false) {
|
||||
|
@ -315,12 +315,12 @@ class Followers
|
|||
// Log the response for debugging if needed
|
||||
if ($response === false) {
|
||||
self::removeFollow($dbh, $sourceUser, $fedUser->id);
|
||||
throw new \Exception('Failed to send Follow activity: ' . curl_error($ch));
|
||||
throw new \Exception("Failed to send Follow activity: " . curl_error($ch));
|
||||
} else {
|
||||
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
if ($httpcode != 200 && $httpcode != 202) {
|
||||
self::removeFollow($dbh, $sourceUser, $fedUser->id);
|
||||
throw new \Exception('Unexpected HTTP code ' . $httpcode . ':' . $response);
|
||||
throw new \Exception("Unexpected HTTP code $httpcode: $response");
|
||||
}
|
||||
}
|
||||
return $idUrl;
|
||||
|
@ -341,9 +341,9 @@ class Followers
|
|||
$sql = 'select id from follows where source_user = ? and target_user = ?';
|
||||
$stmt = $dbh->prepare($sql);
|
||||
if ($stmt === false) {
|
||||
throw new \Federator\Exceptions\ServerError('Followers::addFollow Failed to prepare statement');
|
||||
throw new \Federator\Exceptions\ServerError("Followers::addFollow Failed to prepare statement");
|
||||
}
|
||||
$stmt->bind_param('ss', $sourceUser, $targetUserId);
|
||||
$stmt->bind_param("ss", $sourceUser, $targetUserId);
|
||||
$foundId = 0;
|
||||
$ret = $stmt->bind_result($foundId);
|
||||
$stmt->execute();
|
||||
|
@ -364,10 +364,9 @@ class Followers
|
|||
$sql = 'select id from follows where id = ?';
|
||||
$stmt = $dbh->prepare($sql);
|
||||
if ($stmt === false) {
|
||||
throw new \Federator\Exceptions\ServerError('Followers::addFollow Failed to prepare id-check'
|
||||
. 'statement');
|
||||
throw new \Federator\Exceptions\ServerError("Followers::addFollow Failed to prepare id-check statement");
|
||||
}
|
||||
$stmt->bind_param('s', $idurl);
|
||||
$stmt->bind_param("s", $idurl);
|
||||
$foundId = 0;
|
||||
$ret = $stmt->bind_result($foundId);
|
||||
$stmt->execute();
|
||||
|
@ -381,9 +380,9 @@ class Followers
|
|||
$sql = 'insert into follows (id, source_user, target_user, created_at) values (?, ?, ?, NOW())';
|
||||
$stmt = $dbh->prepare($sql);
|
||||
if ($stmt === false) {
|
||||
throw new \Federator\Exceptions\ServerError('Followers::addFollow Failed to prepare insert statement');
|
||||
throw new \Federator\Exceptions\ServerError("Followers::addFollow Failed to prepare insert statement");
|
||||
}
|
||||
$stmt->bind_param('sss', $idurl, $sourceUser, $targetUserId);
|
||||
$stmt->bind_param("sss", $idurl, $sourceUser, $targetUserId);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
return $idurl; // Return the generated follow ID
|
||||
|
@ -404,9 +403,9 @@ class Followers
|
|||
$sql = 'select id from follows where source_user = ? and target_user = ?';
|
||||
$stmt = $dbh->prepare($sql);
|
||||
if ($stmt === false) {
|
||||
throw new \Federator\Exceptions\ServerError('Followers::addExternalFollow Failed to prepare statement');
|
||||
throw new \Federator\Exceptions\ServerError("Followers::addExternalFollow Failed to prepare statement");
|
||||
}
|
||||
$stmt->bind_param('ss', $sourceUserId, $targetUserId);
|
||||
$stmt->bind_param("ss", $sourceUserId, $targetUserId);
|
||||
$foundId = 0;
|
||||
$ret = $stmt->bind_result($foundId);
|
||||
$stmt->execute();
|
||||
|
@ -422,10 +421,9 @@ class Followers
|
|||
$sql = 'insert into follows (id, source_user, target_user, created_at) values (?, ?, ?, NOW())';
|
||||
$stmt = $dbh->prepare($sql);
|
||||
if ($stmt === false) {
|
||||
throw new \Federator\Exceptions\ServerError('Followers::addExternalFollow Failed to prepare insert '
|
||||
. 'statement');
|
||||
throw new \Federator\Exceptions\ServerError("Followers::addExternalFollow Failed to prepare insert statement");
|
||||
}
|
||||
$stmt->bind_param('sss', $followId, $sourceUserId, $targetUserId);
|
||||
$stmt->bind_param("sss", $followId, $sourceUserId, $targetUserId);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
return true;
|
||||
|
@ -449,10 +447,9 @@ class Followers
|
|||
$sql = 'select id from follows where id = ?';
|
||||
$stmt = $dbh->prepare($sql);
|
||||
if ($stmt === false) {
|
||||
throw new \Federator\Exceptions\ServerError('Followers::generateNewFollowId Failed to prepare id-check'
|
||||
. ' statement');
|
||||
throw new \Federator\Exceptions\ServerError("Followers::generateNewFollowId Failed to prepare id-check statement");
|
||||
}
|
||||
$stmt->bind_param('s', $newIdUrl);
|
||||
$stmt->bind_param("s", $newIdUrl);
|
||||
$foundId = 0;
|
||||
$ret = $stmt->bind_result($foundId);
|
||||
$stmt->execute();
|
||||
|
@ -479,7 +476,7 @@ class Followers
|
|||
$sql = 'delete from follows where source_user = ? and target_user = ? RETURNING id';
|
||||
$stmt = $dbh->prepare($sql);
|
||||
if ($stmt !== false) {
|
||||
$stmt->bind_param('ss', $sourceUser, $targetUserId);
|
||||
$stmt->bind_param("ss", $sourceUser, $targetUserId);
|
||||
if ($stmt->execute()) {
|
||||
$stmt->bind_result($followId);
|
||||
if ($stmt->fetch() === true) {
|
||||
|
@ -498,10 +495,9 @@ class Followers
|
|||
$sql = 'select id from follows where source_user = ? and target_user = ?';
|
||||
$stmt = $dbh->prepare($sql);
|
||||
if ($stmt === false) {
|
||||
throw new \Federator\Exceptions\ServerError('Followers::removeFollow Failed to prepare select '
|
||||
. 'statement');
|
||||
throw new \Federator\Exceptions\ServerError("Followers::removeFollow Failed to prepare select statement");
|
||||
}
|
||||
$stmt->bind_param('ss', $sourceUser, $targetUserId);
|
||||
$stmt->bind_param("ss", $sourceUser, $targetUserId);
|
||||
$stmt->execute();
|
||||
$stmt->bind_result($followId);
|
||||
$found = $stmt->fetch();
|
||||
|
@ -515,10 +511,9 @@ class Followers
|
|||
$sql = 'delete from follows where source_user = ? and target_user = ?';
|
||||
$stmt = $dbh->prepare($sql);
|
||||
if ($stmt === false) {
|
||||
throw new \Federator\Exceptions\ServerError('Followers::removeFollow Failed to prepare delete '
|
||||
. 'statement');
|
||||
throw new \Federator\Exceptions\ServerError("Followers::removeFollow Failed to prepare delete statement");
|
||||
}
|
||||
$stmt->bind_param('ss', $sourceUser, $targetUserId);
|
||||
$stmt->bind_param("ss", $sourceUser, $targetUserId);
|
||||
$stmt->execute();
|
||||
$affectedRows = $stmt->affected_rows;
|
||||
$stmt->close();
|
||||
|
|
|
@ -75,7 +75,7 @@ class Posts
|
|||
}
|
||||
foreach ($newPosts as $newPost) {
|
||||
if (!isset($existingIds[$newPost->getID()])) {
|
||||
if ($newPost->getID() !== '') {
|
||||
if ($newPost->getID() !== "") {
|
||||
self::savePost($dbh, $userid, $newPost);
|
||||
}
|
||||
if (sizeof($posts) < $limit) {
|
||||
|
@ -94,12 +94,12 @@ class Posts
|
|||
$parsed = parse_url($origin);
|
||||
if (isset($parsed) && isset($parsed['host'])) {
|
||||
$parsedHost = $parsed['host'];
|
||||
if (is_string($parsedHost) && $parsedHost !== '') {
|
||||
if (is_string($parsedHost) && $parsedHost !== "") {
|
||||
$originUrl = $parsedHost;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isset($originUrl) || $originUrl === '') {
|
||||
if (!isset($originUrl) || $originUrl === "") {
|
||||
$originUrl = 'localhost'; // Fallback to localhost if no origin is set
|
||||
}
|
||||
|
||||
|
@ -156,8 +156,7 @@ class Posts
|
|||
*/
|
||||
public static function getPostsFromDb($dbh, $userId, $min, $max, $limit = 20)
|
||||
{
|
||||
$sql = 'SELECT `id`, `url`, `user_id`, `actor`, `type`, `object`, `to`, `cc`, unix_timestamp(`published`) as '
|
||||
. 'published FROM posts WHERE user_id = ?';
|
||||
$sql = 'SELECT `id`, `url`, `user_id`, `actor`, `type`, `object`, `to`, `cc`, unix_timestamp(`published`) as published FROM posts WHERE user_id = ?';
|
||||
$params = [$userId];
|
||||
$types = 's';
|
||||
if ($min > 0) {
|
||||
|
@ -267,7 +266,7 @@ class Posts
|
|||
$publishedStr = $published ? gmdate('Y-m-d H:i:s', $published) : gmdate('Y-m-d H:i:s');
|
||||
|
||||
$stmt->bind_param(
|
||||
'ssssssssss',
|
||||
"ssssssssss",
|
||||
$id,
|
||||
$url,
|
||||
$userId,
|
||||
|
@ -298,7 +297,7 @@ class Posts
|
|||
if ($stmt === false) {
|
||||
throw new \Federator\Exceptions\ServerError();
|
||||
}
|
||||
$stmt->bind_param('s', $id);
|
||||
$stmt->bind_param("s", $id);
|
||||
$stmt->execute();
|
||||
$affectedRows = $stmt->affected_rows;
|
||||
$stmt->close();
|
||||
|
@ -322,7 +321,7 @@ class Posts
|
|||
$object = $post->getObject();
|
||||
if (is_object($object)) {
|
||||
$inReplyTo = $object->getInReplyTo();
|
||||
if ($inReplyTo !== '') {
|
||||
if ($inReplyTo !== "") {
|
||||
$id = $inReplyTo; // Use inReplyTo as ID if it's a string
|
||||
} else {
|
||||
$id = $object->getObject();
|
||||
|
@ -330,7 +329,7 @@ class Posts
|
|||
} elseif (is_string($object)) {
|
||||
$id = $object; // If object is a string, use it directly
|
||||
}
|
||||
$stmt->bind_param('s', $id);
|
||||
$stmt->bind_param("s", $id);
|
||||
$articleId = null;
|
||||
$ret = $stmt->bind_result($articleId);
|
||||
$stmt->execute();
|
||||
|
|
|
@ -1,108 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Sascha Nitsch (grumpydeveloper) https://contentnation.net/@grumpydevelop
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* @author Sascha Nitsch (grumpyveveloper)
|
||||
**/
|
||||
|
||||
namespace Federator\DIO;
|
||||
|
||||
/**
|
||||
* Do the Server2Server communication
|
||||
*/
|
||||
class Server
|
||||
{
|
||||
/**
|
||||
* send activity to federated server
|
||||
*
|
||||
* @param \mysqli $dbh database handle
|
||||
* @param string $ourhost host url of our server (e.g. federator)
|
||||
* @param \Federator\Data\User $sender source user
|
||||
* @param \Federator\Data\FedUser $receiver federated user to receive the activity
|
||||
* @param \Federator\Data\ActivityPub\Common\Activity $activity activity to send
|
||||
* @return boolean true on success
|
||||
*/
|
||||
public static function sendActivity($dbh, $ourhost, $sender, $receiver, $activity)
|
||||
{
|
||||
$receiverInboxUrl = $receiver->inboxURL;
|
||||
|
||||
$json = json_encode($activity, JSON_UNESCAPED_SLASHES);
|
||||
|
||||
if ($json === false) {
|
||||
throw new \Exception('Failed to encode JSON: ' . json_last_error_msg());
|
||||
}
|
||||
$digest = 'SHA-256=' . base64_encode(hash('sha256', $json, true));
|
||||
$date = gmdate('D, d M Y H:i:s') . ' GMT';
|
||||
echo "inboxurl $receiverInboxUrl\n";
|
||||
$parsedReceiverInboxUrl = parse_url($receiverInboxUrl);
|
||||
if ($parsedReceiverInboxUrl === false) {
|
||||
throw new \Exception('Failed to parse URL: ' . $receiverInboxUrl);
|
||||
}
|
||||
|
||||
if (!isset($parsedReceiverInboxUrl['host']) || !isset($parsedReceiverInboxUrl['path'])) {
|
||||
throw new \Exception('Invalid inbox URL: missing host or path');
|
||||
}
|
||||
$extHost = $parsedReceiverInboxUrl['host'];
|
||||
$path = $parsedReceiverInboxUrl['path'];
|
||||
|
||||
// Build the signature string
|
||||
$signatureString = "(request-target): post {$path}\n" .
|
||||
"host: {$extHost}\n" .
|
||||
"date: {$date}\n" .
|
||||
"digest: {$digest}";
|
||||
|
||||
// Get rsa private key
|
||||
$privateKey = \Federator\DIO\User::getrsaprivate($dbh, $sender->id); // OR from DB
|
||||
if ($privateKey === false) {
|
||||
throw new \Exception('Failed to get private key');
|
||||
}
|
||||
$pkeyId = openssl_pkey_get_private($privateKey);
|
||||
|
||||
if ($pkeyId === false) {
|
||||
throw new \Exception('Invalid private key');
|
||||
}
|
||||
echo "signaturestring $signatureString\n";
|
||||
openssl_sign($signatureString, $signature, $pkeyId, OPENSSL_ALGO_SHA256);
|
||||
$signature_b64 = base64_encode($signature);
|
||||
|
||||
// Build keyId (public key ID from your actor object)
|
||||
$keyId = $ourhost . '/' . $sender->id . '#main-key';
|
||||
|
||||
$signatureHeader = 'keyId="' . $keyId
|
||||
. '",algorithm="rsa-sha256",headers="(request-target) host date digest",signature="' . $signature_b64 . '"';
|
||||
|
||||
$ch = curl_init($receiverInboxUrl);
|
||||
if ($ch === false) {
|
||||
throw new \Exception('Failed to initialize cURL');
|
||||
}
|
||||
$headers = [
|
||||
'Host: ' . $extHost,
|
||||
'Date: ' . $date,
|
||||
'Digest: ' . $digest,
|
||||
'Content-Type: application/activity+json',
|
||||
'Signature: ' . $signatureHeader,
|
||||
'Accept: application/activity+json',
|
||||
];
|
||||
print_r($headers);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $json);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
$response = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($response === false) {
|
||||
throw new \Exception('Failed to send activity: ' . curl_error($ch));
|
||||
} else {
|
||||
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
if ($httpcode != 200 && $httpcode != 202) {
|
||||
throw new \Exception('Unexpected HTTP code ' . $httpcode . ':' . $response);
|
||||
}
|
||||
}
|
||||
if ($response !== true) {
|
||||
error_log($response);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -17,8 +17,7 @@ class Stats
|
|||
/**
|
||||
* get remote stats
|
||||
*
|
||||
* @param \Federator\Main $main
|
||||
* main instance
|
||||
* @param \Federator\Main $main main instance
|
||||
* @return \Federator\Data\Stats
|
||||
*/
|
||||
public static function getStats($main)
|
||||
|
|
|
@ -26,9 +26,9 @@ class User
|
|||
$sql = 'select unix_timestamp(`validuntil`) from users where id=?';
|
||||
$stmt = $dbh->prepare($sql);
|
||||
if ($stmt === false) {
|
||||
throw new \Federator\Exceptions\ServerError('User::addLocalUser Failed to prepare statement');
|
||||
throw new \Federator\Exceptions\ServerError("User::addLocalUser Failed to prepare statement");
|
||||
}
|
||||
$stmt->bind_param('s', $_user);
|
||||
$stmt->bind_param("s", $_user);
|
||||
$validuntil = 0;
|
||||
$ret = $stmt->bind_result($validuntil);
|
||||
$stmt->execute();
|
||||
|
@ -50,11 +50,11 @@ class User
|
|||
$sql .= ' values (?, ?, ?, ?, now() + interval 1 day, ?, ?, ?, ?, ?, ?, ?, ?)';
|
||||
$stmt = $dbh->prepare($sql);
|
||||
if ($stmt === false) {
|
||||
throw new \Federator\Exceptions\ServerError('User::addLocalUser Failed to prepare create statement');
|
||||
throw new \Federator\Exceptions\ServerError("User::addLocalUser Failed to prepare create statement");
|
||||
}
|
||||
$registered = gmdate('Y-m-d H:i:s', $user->registered);
|
||||
$stmt->bind_param(
|
||||
'ssssssssssss',
|
||||
"ssssssssssss",
|
||||
$_user,
|
||||
$user->externalid,
|
||||
$public,
|
||||
|
@ -74,11 +74,11 @@ class User
|
|||
$sql .= ' iconmediatype=?, iconurl=?, imagemediatype=?, imageurl=? where id=?';
|
||||
$stmt = $dbh->prepare($sql);
|
||||
if ($stmt === false) {
|
||||
throw new \Federator\Exceptions\ServerError('User::addLocalUser Failed to prepare update statement');
|
||||
throw new \Federator\Exceptions\ServerError("User::addLocalUser Failed to prepare update statement");
|
||||
}
|
||||
$registered = gmdate('Y-m-d H:i:s', $user->registered);
|
||||
$stmt->bind_param(
|
||||
'sssssssss',
|
||||
"sssssssss",
|
||||
$user->type,
|
||||
$user->name,
|
||||
$user->summary,
|
||||
|
@ -107,12 +107,12 @@ class User
|
|||
*/
|
||||
public static function getrsaprivate(\mysqli $dbh, string $_user)
|
||||
{
|
||||
$sql = 'select rsaprivate from users where id=?';
|
||||
$sql = "select rsaprivate from users where id=?";
|
||||
$stmt = $dbh->prepare($sql);
|
||||
if ($stmt === false) {
|
||||
throw new \Federator\Exceptions\ServerError('User::getrsaprivate Failed to prepare statement');
|
||||
throw new \Federator\Exceptions\ServerError("User::getrsaprivate Failed to prepare statement");
|
||||
}
|
||||
$stmt->bind_param('s', $_user);
|
||||
$stmt->bind_param("s", $_user);
|
||||
$ret = $stmt->bind_result($rsaPrivateKey);
|
||||
$stmt->execute();
|
||||
if ($ret) {
|
||||
|
@ -136,7 +136,7 @@ class User
|
|||
$sql = 'select id,unix_timestamp(`validuntil`) from users where id=?';
|
||||
$stmt = $dbh->prepare($sql);
|
||||
if ($stmt === false) {
|
||||
throw new \Federator\Exceptions\ServerError('User::extendUser Failed to prepare statement');
|
||||
throw new \Federator\Exceptions\ServerError("User::extendUser Failed to prepare statement");
|
||||
}
|
||||
$stmt->bind_param("s", $_user);
|
||||
$validuntil = 0;
|
||||
|
@ -170,6 +170,7 @@ class User
|
|||
public static function getUserByName($dbh, $_name, $connector, $cache)
|
||||
{
|
||||
$user = false;
|
||||
|
||||
// ask cache
|
||||
if ($cache !== null) {
|
||||
$user = $cache->getRemoteUserByName($_name);
|
||||
|
@ -182,9 +183,9 @@ class User
|
|||
$sql .= 'iconmediatype,iconurl,imagemediatype,imageurl from users where id=? and validuntil>=now()';
|
||||
$stmt = $dbh->prepare($sql);
|
||||
if ($stmt === false) {
|
||||
throw new \Federator\Exceptions\ServerError('User::getUserByName Failed to prepare statement');
|
||||
throw new \Federator\Exceptions\ServerError("User::getUserByName Failed to prepare statement");
|
||||
}
|
||||
$stmt->bind_param('s', $_name);
|
||||
$stmt->bind_param("s", $_name);
|
||||
$user = new \Federator\Data\User();
|
||||
$ret = $stmt->bind_result(
|
||||
$user->id,
|
||||
|
@ -204,15 +205,18 @@ class User
|
|||
$stmt->fetch();
|
||||
}
|
||||
$stmt->close();
|
||||
|
||||
if ($user->id === null) {
|
||||
// ask connector for user-id
|
||||
$ruser = $connector->getRemoteUserByName($_name);
|
||||
if ($ruser !== false) {
|
||||
$user = $ruser;
|
||||
self::addLocalUser($dbh, $user, $_name);
|
||||
}
|
||||
}
|
||||
if ($cache !== null) {
|
||||
if ($user->id === null && $user->externalid !== null) {
|
||||
self::addLocalUser($dbh, $user, $_name);
|
||||
}
|
||||
$cache->saveRemoteUserByName($_name, $user);
|
||||
}
|
||||
return $user;
|
||||
|
|
|
@ -30,7 +30,7 @@ class Votes
|
|||
if ($stmt === false) {
|
||||
throw new \Federator\Exceptions\ServerError();
|
||||
}
|
||||
$stmt->bind_param('sss', $userId, $targetId, $type);
|
||||
$stmt->bind_param("sss", $userId, $targetId, $type);
|
||||
$foundId = 0;
|
||||
$ret = $stmt->bind_result($foundId);
|
||||
$stmt->execute();
|
||||
|
@ -51,7 +51,7 @@ class Votes
|
|||
if ($stmt === false) {
|
||||
throw new \Federator\Exceptions\ServerError();
|
||||
}
|
||||
$stmt->bind_param('s', $id);
|
||||
$stmt->bind_param("s", $id);
|
||||
$foundId = 0;
|
||||
$ret = $stmt->bind_result($foundId);
|
||||
$stmt->execute();
|
||||
|
@ -67,7 +67,7 @@ class Votes
|
|||
if ($stmt === false) {
|
||||
throw new \Federator\Exceptions\ServerError();
|
||||
}
|
||||
$stmt->bind_param('ssss', $id, $userId, $targetId, $type);
|
||||
$stmt->bind_param("ssss", $id, $userId, $targetId, $type);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
return $id; // Return the generated vote ID
|
||||
|
@ -88,7 +88,7 @@ class Votes
|
|||
if ($stmt === false) {
|
||||
throw new \Federator\Exceptions\ServerError();
|
||||
}
|
||||
$stmt->bind_param('ss', $userId, $targetId);
|
||||
$stmt->bind_param("ss", $userId, $targetId);
|
||||
$stmt->execute();
|
||||
$affectedRows = $stmt->affected_rows;
|
||||
$stmt->close();
|
||||
|
|
|
@ -52,7 +52,7 @@ class InboxJob extends \Federator\Api
|
|||
*/
|
||||
public function perform(): bool
|
||||
{
|
||||
error_log('InboxJob: Starting job');
|
||||
error_log("InboxJob: Starting job");
|
||||
$user = $this->args['user'];
|
||||
$recipientId = $this->args['recipientId'];
|
||||
$activity = $this->args['activity'];
|
||||
|
@ -60,19 +60,11 @@ class InboxJob extends \Federator\Api
|
|||
$inboxActivity = \Federator\Data\ActivityPub\Factory::newActivityFromJson($activity);
|
||||
|
||||
if ($inboxActivity === false) {
|
||||
error_log('InboxJob: Failed to create inboxActivity from JSON');
|
||||
error_log("InboxJob: Failed to create inboxActivity from JSON");
|
||||
return false;
|
||||
}
|
||||
|
||||
\Federator\DIO\FedUser::inboxForUser(
|
||||
$this,
|
||||
$this->dbh,
|
||||
$this->connector,
|
||||
$this->cache,
|
||||
$user,
|
||||
$recipientId,
|
||||
$inboxActivity
|
||||
);
|
||||
\Federator\Api\FedUsers\Inbox::postForUser($this->dbh, $this->connector, $this->cache, $user, $recipientId, $inboxActivity);
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -52,7 +52,7 @@ class NewContentJob extends \Federator\Api
|
|||
*/
|
||||
public function perform(): bool
|
||||
{
|
||||
error_log('NewContentJob: Starting job');
|
||||
error_log("NewContentJob: Starting job");
|
||||
$user = $this->args['user'];
|
||||
$recipientId = $this->args['recipientId'];
|
||||
$activity = $this->args['activity'];
|
||||
|
@ -61,23 +61,14 @@ class NewContentJob extends \Federator\Api
|
|||
$activity = \Federator\Data\ActivityPub\Factory::newActivityFromJson($activity);
|
||||
|
||||
if ($activity === false) {
|
||||
error_log('NewContentJob: Failed to create activity from JSON');
|
||||
error_log("NewContentJob: Failed to create activity from JSON");
|
||||
return false;
|
||||
}
|
||||
$domain = $this->config['generic']['externaldomain'];
|
||||
|
||||
$ourUrl = 'https://' . $domain;
|
||||
|
||||
\Federator\Api\V1\NewContent::postForUser(
|
||||
$this->dbh,
|
||||
$this->connector,
|
||||
$this->cache,
|
||||
$ourUrl,
|
||||
$user,
|
||||
$recipientId,
|
||||
$activity,
|
||||
$articleId
|
||||
);
|
||||
\Federator\Api\V1\NewContent::postForUser($this->dbh, $this->connector, $this->cache, $ourUrl, $user, $recipientId, $activity, $articleId);
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -20,9 +20,9 @@ class Language
|
|||
* @var array $validLanguages
|
||||
*/
|
||||
private $validLanguages = array(
|
||||
'de' => true,
|
||||
'en' => true,
|
||||
'xy' => true
|
||||
"de" => true,
|
||||
"en" => true,
|
||||
"xy" => true
|
||||
);
|
||||
|
||||
/**
|
||||
|
@ -92,12 +92,9 @@ class Language
|
|||
}
|
||||
if (! isset($this->lang[$group])) {
|
||||
$l = [];
|
||||
$root = $_SERVER['DOCUMENT_ROOT'];
|
||||
if ($root === '') {
|
||||
$root = '.';
|
||||
}
|
||||
if (@file_exists($root . '../lang/federator/' . $this->uselang . '/' . $group . '.inc')) {
|
||||
require($root . '../lang/federator/' . $this->uselang . '/' . $group . '.inc');
|
||||
$root = PROJECT_ROOT;
|
||||
if (@file_exists($root . '/lang/federator/' . $this->uselang . "/$group.inc")) {
|
||||
require($root . '/lang/federator/' . $this->uselang . "/$group.inc");
|
||||
$this->lang[$group] = $l;
|
||||
}
|
||||
}
|
||||
|
@ -107,15 +104,15 @@ class Language
|
|||
if (isset($values[$i])) {
|
||||
$string = str_replace("\$$i", $values[$i], $string);
|
||||
} else {
|
||||
$string = str_replace("\$$i", '', $string);
|
||||
$string = str_replace("\$$i", "", $string);
|
||||
}
|
||||
}
|
||||
return $string;
|
||||
}
|
||||
$basedir = $_SERVER['DOCUMENT_ROOT'] . '/../';
|
||||
$fh = @fopen($basedir . '/logs/missingtrans.txt', 'a');
|
||||
$basedir = PROJECT_ROOT;
|
||||
$fh = @fopen("$basedir/logs/missingtrans.txt", 'a');
|
||||
if ($fh !== false) {
|
||||
fwrite($fh, $this->uselang . ':' . $group . ':' . "$key\n");
|
||||
fwrite($fh, $this->uselang.":$group:$key\n");
|
||||
fclose($fh);
|
||||
}
|
||||
return ">>$group:$key<<";
|
||||
|
@ -132,7 +129,7 @@ class Language
|
|||
{
|
||||
if (! isset($this->lang[$group])) {
|
||||
$l = [];
|
||||
require_once($_SERVER['DOCUMENT_ROOT'] . '/../lang/' . $this->uselang . '/' . $group . '.inc');
|
||||
require_once(PROJECT_ROOT . '/lang/' . $this->uselang . "/$group.inc");
|
||||
$this->lang[$group] = $l;
|
||||
}
|
||||
// @phan-suppress-next-line PhanPartialTypeMismatchReturn
|
||||
|
@ -288,7 +285,7 @@ function smarty_function_printlang($params, $template) : string
|
|||
*/
|
||||
function smarty_function_printjslang($params, $template) : string
|
||||
{
|
||||
$lang = $template->getTemplateVars('language');
|
||||
$lang = $template->getTemplateVars("language");
|
||||
$prefix = 'window.translations.' . $params['group'] . '.' . $params['key'] . ' = \'';
|
||||
$postfix = '\';';
|
||||
if (isset($params['var'])) {
|
||||
|
|
|
@ -20,43 +20,37 @@ class Main
|
|||
*
|
||||
* @var Cache\Cache $cache
|
||||
*/
|
||||
protected $cache = null;
|
||||
|
||||
protected $cache;
|
||||
/**
|
||||
* current config
|
||||
*
|
||||
* @var array<string,mixed> $config
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* remote connector
|
||||
*
|
||||
* @var Connector\Connector $connector
|
||||
*/
|
||||
protected $connector = null;
|
||||
|
||||
/**
|
||||
* remote host (f.e. https://contentnation.net)
|
||||
*
|
||||
* @var string $host
|
||||
*/
|
||||
protected $host = null;
|
||||
|
||||
/**
|
||||
* response content type
|
||||
*
|
||||
* @var string $contentType
|
||||
*/
|
||||
protected $contentType = 'text/html';
|
||||
|
||||
protected $contentType = "text/html";
|
||||
/**
|
||||
* database instance
|
||||
*
|
||||
* @var \Mysqli $dbh
|
||||
*/
|
||||
protected $dbh;
|
||||
|
||||
/**
|
||||
* extra headers
|
||||
*
|
||||
|
@ -84,9 +78,9 @@ class Main
|
|||
*/
|
||||
public function __construct()
|
||||
{
|
||||
require_once($_SERVER['DOCUMENT_ROOT'] . '../vendor/autoload.php');
|
||||
require_once(PROJECT_ROOT . '/vendor/autoload.php');
|
||||
$this->responseCode = 200;
|
||||
$rootDir = $_SERVER['DOCUMENT_ROOT'] . '../';
|
||||
$rootDir = PROJECT_ROOT . '/';
|
||||
$config = parse_ini_file($rootDir . 'config.ini', true);
|
||||
if ($config !== false) {
|
||||
$this->config = $config;
|
||||
|
@ -151,7 +145,6 @@ class Main
|
|||
{
|
||||
return $this->cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* get connector
|
||||
*
|
||||
|
@ -161,7 +154,6 @@ class Main
|
|||
{
|
||||
return $this->connector;
|
||||
}
|
||||
|
||||
/**
|
||||
* get host (f.e. https://contentnation.net)
|
||||
*
|
||||
|
@ -197,7 +189,7 @@ class Main
|
|||
public function loadPlugins(): void
|
||||
{
|
||||
if (array_key_exists('plugins', $this->config)) {
|
||||
$basepath = $_SERVER['DOCUMENT_ROOT'] . '../plugins/federator/';
|
||||
$basepath = PROJECT_ROOT . '/plugins/federator/';
|
||||
$plugins = $this->config['plugins'];
|
||||
foreach ($plugins as $name => $file) {
|
||||
require_once($basepath . $file);
|
||||
|
@ -218,7 +210,7 @@ class Main
|
|||
*/
|
||||
public function openDatabase($usernameOverride = null, $passwordOverride = null)
|
||||
{
|
||||
$dbconf = $this->config['database'];
|
||||
$dbconf = $this->config["database"];
|
||||
$this->dbh = new \mysqli(
|
||||
$dbconf['host'],
|
||||
$usernameOverride ?? (string) $dbconf['username'],
|
||||
|
@ -240,10 +232,10 @@ class Main
|
|||
*/
|
||||
public function renderTemplate($template, $data)
|
||||
{
|
||||
$rootDir = PROJECT_ROOT . '/';
|
||||
$smarty = new \Smarty\Smarty();
|
||||
$root = $_SERVER['DOCUMENT_ROOT'];
|
||||
$smarty->setCompileDir($root . $this->config['templates']['compiledir']);
|
||||
$smarty->setTemplateDir((string)realpath($root . $this->config['templates']['path']));
|
||||
$smarty->setCompileDir($rootDir . $this->config['templates']['compiledir']);
|
||||
$smarty->setTemplateDir((string) realpath($rootDir . $this->config['templates']['path']));
|
||||
$smarty->assign('database', $this->dbh);
|
||||
$smarty->assign('maininstance', $this);
|
||||
foreach ($data as $key => $value) {
|
||||
|
@ -269,6 +261,9 @@ class Main
|
|||
*/
|
||||
public function setConnector(Connector\Connector $connector) : void
|
||||
{
|
||||
if (isset($this->connector)) {
|
||||
# echo "main::setConnector Setting new connector will override old one.\n"; // TODO CHANGE TO LOG WARNING
|
||||
}
|
||||
$this->connector = $connector;
|
||||
}
|
||||
|
||||
|
@ -279,6 +274,9 @@ class Main
|
|||
*/
|
||||
public function setHost(string $host) : void
|
||||
{
|
||||
if (isset($this->host)) {
|
||||
# echo "main::setHost Setting new host will override old one.\n"; // TODO CHANGE TO LOG WARNING
|
||||
}
|
||||
$this->host = $host;
|
||||
}
|
||||
|
||||
|
|
|
@ -22,16 +22,15 @@ class Maintenance
|
|||
*/
|
||||
public static function run($argc, $argv)
|
||||
{
|
||||
date_default_timezone_set('Europe/Berlin');
|
||||
date_default_timezone_set("Europe/Berlin");
|
||||
spl_autoload_register(static function (string $className) {
|
||||
$root = $_SERVER['DOCUMENT_ROOT'];
|
||||
include $root . '../php/' . str_replace("\\", "/", strtolower($className)) . '.php';
|
||||
include PROJECT_ROOT . '/php/' . str_replace("\\", "/", strtolower($className)) . '.php';
|
||||
});
|
||||
if ($argc < 2) {
|
||||
self::printUsage();
|
||||
}
|
||||
// pretend that we are running from web directory
|
||||
$_SERVER['DOCUMENT_ROOT'] = realpath('../../htdocs') . '/';
|
||||
define('PROJECT_ROOT', dirname(__DIR__, 2));
|
||||
$main = new \Federator\Main();
|
||||
switch ($argv[1]) {
|
||||
case 'dbupgrade':
|
||||
|
@ -71,7 +70,7 @@ class Maintenance
|
|||
}
|
||||
}
|
||||
echo "current version: $version\n";
|
||||
$root = $_SERVER['DOCUMENT_ROOT'] . '../';
|
||||
$root = PROJECT_ROOT . '/';
|
||||
$updateFolder = opendir($root . 'sql');
|
||||
if ($updateFolder === false) {
|
||||
die();
|
||||
|
|
|
@ -81,7 +81,7 @@ class Test
|
|||
*/
|
||||
public static function run($argc, $argv)
|
||||
{
|
||||
date_default_timezone_set('Europe/Berlin');
|
||||
date_default_timezone_set("Europe/Berlin");
|
||||
spl_autoload_register(static function (string $className) {
|
||||
include PROJECT_ROOT . '/php/' . str_replace("\\", "/", strtolower($className)) . '.php';
|
||||
});
|
||||
|
@ -143,8 +143,7 @@ class Test
|
|||
$inboxActivity->setAActor('https://mastodon.local/users/admin');
|
||||
$inboxActivity->setObject($_url);
|
||||
$inboxActivity->setID("https://mastodon.local/users/admin#like/" . md5($_url));
|
||||
\Federator\DIO\FedUser::inboxForUser(
|
||||
$api,
|
||||
\Federator\Api\FedUsers\Inbox::postForUser(
|
||||
$dbh,
|
||||
$api->getConnector(),
|
||||
null,
|
||||
|
|
|
@ -1,18 +1,14 @@
|
|||
<?php
|
||||
|
||||
define('PROJECT_ROOT', dirname(__DIR__, 3));
|
||||
require_once PROJECT_ROOT . '/vendor/autoload.php';
|
||||
$_SERVER['DOCUMENT_ROOT'] = PROJECT_ROOT . '/htdocs/';
|
||||
|
||||
spl_autoload_register(static function (string $className) {
|
||||
include PROJECT_ROOT . '/php/' . str_replace("\\", "/", strtolower($className)) . '.php';
|
||||
});
|
||||
require_once PROJECT_ROOT . '/vendor/autoload.php';
|
||||
|
||||
$config = parse_ini_file(PROJECT_ROOT . '/rediscache.ini');
|
||||
|
||||
// Set the Redis backend for Resque
|
||||
$redisUrl = sprintf(
|
||||
'redis://%s:%s@%s:%d?password-encoding=u',
|
||||
'redis://%s:%s@%s:%d',
|
||||
urlencode($config['username']),
|
||||
urlencode($config['password']),
|
||||
$config['host'],
|
||||
|
|
|
@ -41,7 +41,7 @@ class ContentNation implements Connector
|
|||
*/
|
||||
public function __construct($main)
|
||||
{
|
||||
$config = parse_ini_file($_SERVER['DOCUMENT_ROOT'] . '../contentnation.ini', true);
|
||||
$config = parse_ini_file(PROJECT_ROOT . '/contentnation.ini', true);
|
||||
if ($config !== false) {
|
||||
$this->config = $config;
|
||||
}
|
||||
|
@ -53,28 +53,26 @@ class ContentNation implements Connector
|
|||
/**
|
||||
* get followers of given user
|
||||
*
|
||||
* @param string $userId user id @unused-param
|
||||
* @param string $userId user id
|
||||
* @return \Federator\Data\FedUser[]|false
|
||||
*/
|
||||
public function getFollowersByUser($userId)
|
||||
public function getRemoteFollowersOfUser($userId)
|
||||
{
|
||||
// ContentNation does not export followers
|
||||
/*
|
||||
// todo implement queue for this
|
||||
if (preg_match("#^([^@]+)@([^/]+)#", $userId, $matches) == 1) {
|
||||
$userId = $matches[1];
|
||||
}
|
||||
$remoteURL = $this->service . '/api/profile/' . urlencode($userId);# . '/followers';
|
||||
$remoteURL = $this->service . '/api/profile/' . urlencode($userId) . '/followers';
|
||||
|
||||
[$response, $info] = \Federator\Main::getFromRemote($remoteURL, []);
|
||||
if ($info['http_code'] != 200) {
|
||||
error_log("ContentNation::getFollowersByUser error retrieving followers for userId: $userId . Error: "
|
||||
. json_encode($info));
|
||||
error_log("ContentNation::getRemoteFollowersOfUser error retrieving followers for userId: $userId . Error: " . json_encode($info));
|
||||
return false;
|
||||
}
|
||||
$r = json_decode($response, true);
|
||||
if ($r === false || $r === null || !is_array($r)) {
|
||||
return false;
|
||||
}*/
|
||||
}
|
||||
$followers = [];
|
||||
return $followers;
|
||||
}
|
||||
|
@ -82,13 +80,13 @@ class ContentNation implements Connector
|
|||
/**
|
||||
* get following of given user
|
||||
*
|
||||
* @param string $userId user id @unused-param
|
||||
* @param string $userId user id
|
||||
|
||||
* @return \Federator\Data\FedUser[]|false
|
||||
*/
|
||||
public function getFollowingByUser($userId)
|
||||
public function getRemoteFollowingForUser($userId)
|
||||
{
|
||||
// ContentNation does not export Following for user
|
||||
/*
|
||||
// todo implement queue for this
|
||||
if (preg_match("#^([^@]+)@([^/]+)#", $userId, $matches) == 1) {
|
||||
$userId = $matches[1];
|
||||
}
|
||||
|
@ -96,16 +94,15 @@ class ContentNation implements Connector
|
|||
|
||||
[$response, $info] = \Federator\Main::getFromRemote($remoteURL, []);
|
||||
if ($info['http_code'] != 200) {
|
||||
error_log('ContentNation::getRemoteFollowingForUser error retrieving following for userId: ' . $userId
|
||||
. '. Error: ' . json_encode($info));
|
||||
error_log("ContentNation::getRemoteFollowingForUser error retrieving following for userId: $userId . Error: " . json_encode($info));
|
||||
return false;
|
||||
}
|
||||
$r = json_decode($response, true);
|
||||
if ($r === false || $r === null || !is_array($r)) {
|
||||
return false;
|
||||
}*/
|
||||
$following = [];
|
||||
return $following;
|
||||
}
|
||||
$followers = [];
|
||||
return $followers;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -132,8 +129,7 @@ class ContentNation implements Connector
|
|||
}
|
||||
[$response, $info] = \Federator\Main::getFromRemote($remoteURL, []);
|
||||
if ($info['http_code'] != 200) {
|
||||
error_log('ContentNation::getRemotePostsByUser error retrieving activities for userId: ' . $userId
|
||||
. '. Error: ' . json_encode($info));
|
||||
error_log("ContentNation::getRemotePostsByUser error retrieving activities for userId: $userId . Error: " . json_encode($info));
|
||||
return false;
|
||||
}
|
||||
$r = json_decode($response, true);
|
||||
|
@ -215,8 +211,7 @@ class ContentNation implements Connector
|
|||
$commentJson = $activity;
|
||||
$commentJson['type'] = 'Note';
|
||||
$commentJson['summary'] = $activity['subject'];
|
||||
$commentJson['id'] = $ourUrl . '/' . $activity['articleOwnerName'] . '/'
|
||||
. $activity['articleName'] . '#' . $activity['id'];
|
||||
$commentJson['id'] = $ourUrl . '/' . $activity['articleOwnerName'] . '/' . $activity['articleName'] . '#' . $activity['id'];
|
||||
$note = \Federator\Data\ActivityPub\Factory::newFromJson($commentJson, "");
|
||||
if ($note === null) {
|
||||
error_log("ContentNation::getRemotePostsByUser couldn't create comment");
|
||||
|
@ -226,14 +221,11 @@ class ContentNation implements Connector
|
|||
}
|
||||
$note->setID($commentJson['id']);
|
||||
if (!isset($commentJson['parent']) || $commentJson['parent'] === null) {
|
||||
$note->setInReplyTo($ourUrl . '/' . $activity['articleOwnerName'] . '/'
|
||||
. $activity['articleName']);
|
||||
$note->setInReplyTo($ourUrl . '/' . $activity['articleOwnerName'] . '/' . $activity['articleName']);
|
||||
} else {
|
||||
$note->setInReplyTo($ourUrl . '/' . $activity['articleOwnerName'] . '/'
|
||||
. $activity['articleName'] . "#" . $commentJson['parent']);
|
||||
$note->setInReplyTo($ourUrl . '/' . $activity['articleOwnerName'] . '/' . $activity['articleName'] . "#" . $commentJson['parent']);
|
||||
}
|
||||
$url = $ourUrl . '/' . $activity['articleOwnerName'] . '/' . $activity['articleName']
|
||||
. '#' . $activity['id'];
|
||||
$url = $ourUrl . '/' . $activity['articleOwnerName'] . '/' . $activity['articleName'] . '#' . $activity['id'];
|
||||
$create->setURL($url);
|
||||
$create->setID($url);
|
||||
$create->setObject($note);
|
||||
|
@ -301,10 +293,16 @@ class ContentNation implements Connector
|
|||
public function getRemoteUserByName(string $_name)
|
||||
{
|
||||
// validate name
|
||||
if (preg_match("/^[a-zA-Z0-9\._\-]+$/", $_name) != 1) {
|
||||
if (preg_match("/^[a-zA-Z@0-9\._\-]+$/", $_name) != 1) {
|
||||
return false;
|
||||
}
|
||||
$remoteURL = $this->service . '/api/users/info?user=' . urlencode($_name);
|
||||
// make sure we only get name part, without domain
|
||||
if (preg_match("#^([^@]+)@([^/]+)#", $_name, $matches) == 1) {
|
||||
$name = $matches[1];
|
||||
} else {
|
||||
$name = $_name;
|
||||
}
|
||||
$remoteURL = $this->service . '/api/users/info?user=' . urlencode($name);
|
||||
$headers = ['Accept: application/json'];
|
||||
[$response, $info] = \Federator\Main::getFromRemote($remoteURL, $headers);
|
||||
if ($info['http_code'] != 200) {
|
||||
|
@ -341,7 +339,7 @@ class ContentNation implements Connector
|
|||
if (preg_match("/^[a-z0-9]{16}$/", $_session) != 1) {
|
||||
return false;
|
||||
}
|
||||
if (preg_match("/^[a-zA-Z0-9_\-]+$/", $_user) != 1) {
|
||||
if (preg_match("/^[a-zA-Z@0-9\._\-]+$/", $_user) != 1) {
|
||||
return false;
|
||||
}
|
||||
$remoteURL = $this->service . '/api/users/permissions?profile=' . urlencode($_user);
|
||||
|
@ -430,8 +428,7 @@ class ContentNation implements Connector
|
|||
} elseif ($jsonData['object']['vote']['value'] == 0) {
|
||||
$ap['object']['type'] = 'Dislike';
|
||||
} else {
|
||||
error_log('ContentNation::jsonToActivity unknown vote value: '
|
||||
. $jsonData['object']['vote']['value']);
|
||||
error_log("ContentNation::jsonToActivity unknown vote value: {$jsonData['object']['vote']['value']}");
|
||||
break;
|
||||
}
|
||||
$ap['object']['object'] = self::generateObjectJson($ourUrl, $jsonData);
|
||||
|
@ -441,7 +438,7 @@ class ContentNation implements Connector
|
|||
}
|
||||
$returnActivity = \Federator\Data\ActivityPub\Factory::newActivityFromJson($ap);
|
||||
if ($returnActivity === false) {
|
||||
error_log('ContentNation::jsonToActivity couldn\'t create undo');
|
||||
error_log("ContentNation::jsonToActivity couldn't create undo");
|
||||
$returnActivity = new \Federator\Data\ActivityPub\Common\Undo();
|
||||
} else {
|
||||
$returnActivity->setID($ap['id']);
|
||||
|
@ -450,8 +447,7 @@ class ContentNation implements Connector
|
|||
break;
|
||||
default:
|
||||
// Handle unsupported types or fallback to default behavior
|
||||
throw new \InvalidArgumentException('ContentNation::jsonToActivity Unsupported type: '
|
||||
. $jsonData['type']);
|
||||
throw new \InvalidArgumentException("ContentNation::jsonToActivity Unsupported type: {$jsonData['type']}");
|
||||
}
|
||||
} else {
|
||||
// Handle specific fields based on the type
|
||||
|
@ -484,7 +480,7 @@ class ContentNation implements Connector
|
|||
$ap['object'] = self::generateObjectJson($ourUrl, $jsonData);
|
||||
$returnActivity = \Federator\Data\ActivityPub\Factory::newActivityFromJson($ap);
|
||||
if ($returnActivity === false) {
|
||||
error_log('ContentNation::jsonToActivity couldn\'t create article');
|
||||
error_log("ContentNation::jsonToActivity couldn't create article");
|
||||
$returnActivity = new \Federator\Data\ActivityPub\Common\Activity('Create');
|
||||
} else {
|
||||
$returnActivity->setID($ap['id']);
|
||||
|
@ -517,7 +513,7 @@ class ContentNation implements Connector
|
|||
$ap['object'] = self::generateObjectJson($ourUrl, $jsonData);
|
||||
$returnActivity = \Federator\Data\ActivityPub\Factory::newActivityFromJson($ap);
|
||||
if ($returnActivity === false) {
|
||||
error_log('ContentNation::jsonToActivity couldn\'t create comment');
|
||||
error_log("ContentNation::jsonToActivity couldn't create comment");
|
||||
$returnActivity = new \Federator\Data\ActivityPub\Common\Activity('Create');
|
||||
} else {
|
||||
$returnActivity->setID($ap['id']);
|
||||
|
@ -539,18 +535,17 @@ class ContentNation implements Connector
|
|||
} elseif ($jsonData['object']['vote']['value'] == 0) {
|
||||
$ap['type'] = 'Dislike';
|
||||
} else {
|
||||
error_log('ContentNation::jsonToActivity unknown vote value: '
|
||||
. $jsonData['object']['vote']['value']);
|
||||
error_log("ContentNation::jsonToActivity unknown vote value: {$jsonData['object']['vote']['value']}");
|
||||
break;
|
||||
}
|
||||
|
||||
$ap['object'] = self::generateObjectJson($ourUrl, $jsonData);
|
||||
$returnActivity = \Federator\Data\ActivityPub\Factory::newActivityFromJson($ap);
|
||||
if ($returnActivity === false) {
|
||||
error_log('ContentNation::jsonToActivity couldn\'t create vote');
|
||||
if ($ap['type'] === 'Like') {
|
||||
error_log("ContentNation::jsonToActivity couldn't create vote");
|
||||
if ($ap['type'] === "Like") {
|
||||
$returnActivity = new \Federator\Data\ActivityPub\Common\Like();
|
||||
} elseif ($ap['type'] === 'Dislike') {
|
||||
} elseif ($ap['type'] === "Dislike") {
|
||||
$returnActivity = new \Federator\Data\ActivityPub\Common\Dislike();
|
||||
} else {
|
||||
$returnActivity = new \Federator\Data\ActivityPub\Common\Undo();
|
||||
|
@ -564,8 +559,7 @@ class ContentNation implements Connector
|
|||
|
||||
default:
|
||||
// Handle unsupported types or fallback to default behavior
|
||||
throw new \InvalidArgumentException('ContentNation::jsonToActivity Unsupported object type: '
|
||||
. $jsonData['type']);
|
||||
throw new \InvalidArgumentException("ContentNation::jsonToActivity Unsupported object type: {$jsonData['type']}");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -587,7 +581,7 @@ class ContentNation implements Connector
|
|||
|
||||
$actorUrl = $ourUrl . '/' . $actorName;
|
||||
|
||||
if ($objectType === 'article') {
|
||||
if ($objectType === "article") {
|
||||
$articleName = $jsonData['object']['name'] ?? null;
|
||||
$articleOwnerName = $jsonData['object']['ownerName'] ?? null;
|
||||
$updatedOn = $jsonData['object']['modified'] ?? null;
|
||||
|
@ -595,13 +589,13 @@ class ContentNation implements Connector
|
|||
$update = $updatedOn !== $originalPublished;
|
||||
$returnJson = [
|
||||
'type' => 'Article',
|
||||
'id' => $ourUrl . '/' . $articleOwnerName . '/' . $articleName,
|
||||
'id' => $ourUrl . "/" . $articleOwnerName . "/" . $articleName,
|
||||
'name' => $jsonData['object']['title'] ?? null,
|
||||
'published' => $originalPublished,
|
||||
'summary' => $jsonData['object']['summary'] ?? null,
|
||||
'content' => $jsonData['object']['content'] ?? null,
|
||||
'attributedTo' => $actorUrl,
|
||||
'url' => $ourUrl . '/' . $articleOwnerName . '/' . $articleName,
|
||||
'url' => $ourUrl . "/" . $articleOwnerName . "/" . $articleName,
|
||||
'cc' => ['https://www.w3.org/ns/activitystreams#Public'],
|
||||
];
|
||||
if ($update) {
|
||||
|
@ -625,14 +619,14 @@ class ContentNation implements Connector
|
|||
}
|
||||
}
|
||||
}
|
||||
} elseif ($objectType === 'comment') {
|
||||
} elseif ($objectType === "comment") {
|
||||
$commentId = $jsonData['object']['id'] ?? null;
|
||||
$articleName = $jsonData['object']['articleName'] ?? null;
|
||||
$articleOwnerName = $jsonData['object']['articleOwnerName'] ?? null;
|
||||
$returnJson = [
|
||||
'type' => 'Note',
|
||||
'id' => $ourUrl . '/' . $articleOwnerName . '/' . $articleName . '#' . $commentId,
|
||||
'url' => $ourUrl . '/' . $articleOwnerName . '/' . $articleName . '#' . $commentId,
|
||||
'id' => $ourUrl . "/" . $articleOwnerName . "/" . $articleName . "#" . $commentId,
|
||||
'url' => $ourUrl . "/" . $articleOwnerName . "/" . $articleName . "#" . $commentId,
|
||||
'attributedTo' => $actorUrl,
|
||||
'content' => $jsonData['object']['content'] ?? null,
|
||||
'summary' => $jsonData['object']['summary'] ?? null,
|
||||
|
@ -648,26 +642,24 @@ class ContentNation implements Connector
|
|||
}
|
||||
$replyType = $jsonData['object']['inReplyTo']['type'] ?? null;
|
||||
if ($replyType === "article") {
|
||||
$returnJson['inReplyTo'] = $ourUrl . '/' . $articleOwnerName . '/' . $articleName;
|
||||
$returnJson['inReplyTo'] = $ourUrl . "/" . $articleOwnerName . "/" . $articleName;
|
||||
} elseif ($replyType === "comment") {
|
||||
$returnJson['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);
|
||||
error_log("ContentNation::generateObjectJson for comment - unknown inReplyTo type: {$replyType}");
|
||||
}
|
||||
} elseif ($objectType === 'vote') {
|
||||
} elseif ($objectType === "vote") {
|
||||
$votedOn = $jsonData['object']['type'] ?? null;
|
||||
$articleName = $jsonData['object']['articleName'] ?? null;
|
||||
$articleOwnerName = $jsonData['object']['articleOwnerName'] ?? null;
|
||||
$objectId = $ourUrl . '/' . $articleOwnerName . '/' . $articleName;
|
||||
if ($votedOn === 'comment') {
|
||||
if ($votedOn === "comment") {
|
||||
$objectId .= '#' . $jsonData['object']['commentId'];
|
||||
}
|
||||
|
||||
$returnJson = $objectId;
|
||||
} else {
|
||||
error_log('ContentNation::generateObjectJson unknown object type: ' . $objectType);
|
||||
error_log("ContentNation::generateObjectJson unknown object type: {$objectType}");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -686,16 +678,10 @@ class ContentNation implements Connector
|
|||
$targetUrl = $this->service;
|
||||
$targetRequestType = 'post'; // Default request type
|
||||
// Convert ActivityPub activity to ContentNation JSON format and retrieve target url
|
||||
$jsonData = self::activityToJson(
|
||||
$this->main->getDatabase(),
|
||||
$this->service,
|
||||
$activity,
|
||||
$targetUrl,
|
||||
$targetRequestType
|
||||
);
|
||||
$jsonData = self::activityToJson($this->main->getDatabase(), $this->service, $activity, $targetUrl, $targetRequestType);
|
||||
|
||||
if ($jsonData === false) {
|
||||
error_log('ContentNation::sendActivity failed to convert activity to JSON');
|
||||
error_log("ContentNation::sendActivity failed to convert activity to JSON");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -723,7 +709,7 @@ class ContentNation implements Connector
|
|||
"date: {$date}\n" .
|
||||
"digest: {$digest}";
|
||||
|
||||
$pKeyPath = $_SERVER['DOCUMENT_ROOT'] . '../' . $this->main->getConfig()['keys']['federatorPrivateKeyPath'];
|
||||
$pKeyPath = PROJECT_ROOT . '/' . $this->main->getConfig()['keys']['federatorPrivateKeyPath'];
|
||||
$privateKeyPem = file_get_contents($pKeyPath);
|
||||
if ($privateKeyPem === false) {
|
||||
http_response_code(500);
|
||||
|
@ -739,8 +725,7 @@ class ContentNation implements Connector
|
|||
openssl_sign($signatureString, $signature, $pkeyId, OPENSSL_ALGO_SHA256);
|
||||
$signature_b64 = base64_encode($signature);
|
||||
|
||||
$signatureHeader = 'algorithm="rsa-sha256",headers="(request-target) host date digest",signature="'
|
||||
. $signature_b64 . '"';
|
||||
$signatureHeader = 'algorithm="rsa-sha256",headers="(request-target) host date digest",signature="' . $signature_b64 . '"';
|
||||
|
||||
$ch = curl_init($targetUrl);
|
||||
if ($ch === false) {
|
||||
|
@ -765,8 +750,7 @@ class ContentNation implements Connector
|
|||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
|
||||
break;
|
||||
default:
|
||||
throw new \Exception('ContentNation::sendActivity Unsupported target request type: '
|
||||
. $targetRequestType);
|
||||
throw new \Exception("ContentNation::sendActivity Unsupported target request type: $targetRequestType");
|
||||
}
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $json);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
|
@ -774,11 +758,11 @@ class ContentNation implements Connector
|
|||
curl_close($ch);
|
||||
|
||||
if ($response === false) {
|
||||
throw new \Exception('Failed to send activity: ' . curl_error($ch));
|
||||
throw new \Exception("Failed to send activity: " . curl_error($ch));
|
||||
} else {
|
||||
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
if ($httpcode != 200 && $httpcode != 202) {
|
||||
throw new \Exception('Unexpected HTTP code ' . $httpcode .':' . $response);
|
||||
throw new \Exception("Unexpected HTTP code $httpcode: $response");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -795,7 +779,7 @@ class ContentNation implements Connector
|
|||
* @param string $targetRequestType the target request type (e.g., 'post', 'delete', etc.)
|
||||
* @return array<string, mixed>|false the json data or false on failure
|
||||
*/
|
||||
private function activityToJson($dbh, $serviceUrl, $activity, &$targetUrl, &$targetRequestType)
|
||||
private function activityToJson($dbh, $serviceUrl, \Federator\Data\ActivityPub\Common\Activity $activity, string &$targetUrl, string &$targetRequestType)
|
||||
{
|
||||
$type = strtolower($activity->getType());
|
||||
$targetRequestType = 'post'; // Default request type
|
||||
|
@ -807,14 +791,12 @@ class ContentNation implements Connector
|
|||
$objType = strtolower($object->getType());
|
||||
$articleId = \Federator\DIO\Posts::getOriginalArticleId($dbh, $activity);
|
||||
if ($articleId === null) {
|
||||
error_log('ContentNation::activityToJson Failed to get original article ID'
|
||||
.' for create/update activity');
|
||||
error_log("ContentNation::activityToJson Failed to get original article ID for create/update activity");
|
||||
}
|
||||
switch ($objType) {
|
||||
case 'article':
|
||||
// We don't support article create/update at this point in time
|
||||
error_log('ContentNation::activityToJson Unsupported create/update object type: '
|
||||
.$objType);
|
||||
error_log("ContentNation::activityToJson Unsupported create/update object type: {$objType}");
|
||||
break;
|
||||
case 'note':
|
||||
$targetUrl = $serviceUrl . '/api/article/' . $articleId . '/comment';
|
||||
|
@ -834,8 +816,7 @@ class ContentNation implements Connector
|
|||
}
|
||||
}
|
||||
} else {
|
||||
error_log('ContentNation::activityToJson Unsupported target type for comment with id: '
|
||||
. $activity->getID() . ' Type: ' . gettype($target));
|
||||
error_log("ContentNation::activityToJson Unsupported target type for comment with id: " . $activity->getID() . " Type: " . gettype($target));
|
||||
return false;
|
||||
}
|
||||
return [
|
||||
|
@ -846,8 +827,7 @@ class ContentNation implements Connector
|
|||
'comment' => $object->getContent(),
|
||||
];
|
||||
default:
|
||||
error_log('ContentNation::activityToJson Unsupported create/update object type: '
|
||||
. $objType);
|
||||
error_log("ContentNation::activityToJson Unsupported create/update object type: {$objType}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -856,14 +836,13 @@ class ContentNation implements Connector
|
|||
case 'follow':
|
||||
$profileUrl = $activity->getObject();
|
||||
if (!is_string($profileUrl)) {
|
||||
error_log('ContentNation::activityToJson Invalid profile URL: ' . json_encode($profileUrl));
|
||||
error_log("ContentNation::activityToJson Invalid profile URL: " . json_encode($profileUrl));
|
||||
return false;
|
||||
}
|
||||
$receiverName = basename((string) (parse_url($profileUrl, PHP_URL_PATH) ?? ''));
|
||||
$ourDomain = parse_url($profileUrl, PHP_URL_HOST);
|
||||
if ($receiverName === '' || $ourDomain === '') {
|
||||
error_log('ContentNation::activityToJson no profileName or domain found for object url: '
|
||||
. $profileUrl);
|
||||
if ($receiverName === "" || $ourDomain === "") {
|
||||
error_log("ContentNation::activityToJson no profileName or domain found for object url: " . $profileUrl);
|
||||
return false;
|
||||
}
|
||||
$receiver = $receiverName;
|
||||
|
@ -875,12 +854,11 @@ class ContentNation implements Connector
|
|||
null
|
||||
);
|
||||
} catch (\Throwable $e) {
|
||||
error_log('ContentNation::activityToJson get user by name: ' . $receiver . '. Exception: '
|
||||
. $e->getMessage());
|
||||
error_log("ContentNation::activityToJson get user by name: " . $receiver . ". Exception: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
if ($localUser === null || $localUser->id === null) {
|
||||
error_log('ContentNation::activityToJson couldn\'t find user: ' . $receiver);
|
||||
error_log("ContentNation::activityToJson couldn't find user: $receiver");
|
||||
return false;
|
||||
}
|
||||
$targetUrl = $serviceUrl . '/api/profile/' . $localUser->id . '/fedfollow';
|
||||
|
@ -903,7 +881,7 @@ class ContentNation implements Connector
|
|||
case 'dislike':
|
||||
$articleId = \Federator\DIO\Posts::getOriginalArticleId($dbh, $activity);
|
||||
if ($articleId === null) {
|
||||
error_log('ContentNation::activityToJson Failed to get original article ID for vote activity');
|
||||
error_log("ContentNation::activityToJson Failed to get original article ID for vote activity");
|
||||
}
|
||||
$voteValue = $type === 'like' ? true : false;
|
||||
$activityType = 'vote';
|
||||
|
@ -922,8 +900,7 @@ class ContentNation implements Connector
|
|||
}
|
||||
}
|
||||
} else {
|
||||
error_log('ContentNation::activityToJson Unsupported target type for vote with id: '
|
||||
. $activity->getID() . ' Type: ' . gettype($target));
|
||||
error_log("ContentNation::activityToJson Unsupported target type for vote with id: " . $activity->getID() . " Type: " . gettype($target));
|
||||
return false;
|
||||
}
|
||||
$targetUrl = $serviceUrl . '/api/article/' . $articleId . '/vote';
|
||||
|
@ -942,15 +919,13 @@ class ContentNation implements Connector
|
|||
case 'follow':
|
||||
$profileUrl = $object->getObject();
|
||||
if (!is_string($profileUrl)) {
|
||||
error_log('ContentNation::activityToJson Invalid profile URL: '
|
||||
. json_encode($profileUrl));
|
||||
error_log("ContentNation::activityToJson Invalid profile URL: " . json_encode($profileUrl));
|
||||
return false;
|
||||
}
|
||||
$receiverName = basename((string) (parse_url($profileUrl, PHP_URL_PATH) ?? ''));
|
||||
$ourDomain = parse_url($profileUrl, PHP_URL_HOST);
|
||||
if ($receiverName === '' || $ourDomain === '') {
|
||||
error_log('ContentNation::activityToJson no profileName or domain found for object'
|
||||
. ' url: ' . $profileUrl);
|
||||
if ($receiverName === "" || $ourDomain === "") {
|
||||
error_log("ContentNation::activityToJson no profileName or domain found for object url: " . $profileUrl);
|
||||
return false;
|
||||
}
|
||||
$receiver = $receiverName;
|
||||
|
@ -962,12 +937,11 @@ class ContentNation implements Connector
|
|||
null
|
||||
);
|
||||
} catch (\Throwable $e) {
|
||||
error_log('ContentNation::activityToJson get user by name: ' . $receiver
|
||||
. '. Exception: ' . $e->getMessage());
|
||||
error_log("ContentNation::activityToJson get user by name: " . $receiver . ". Exception: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
if ($localUser === null || $localUser->id === null) {
|
||||
error_log('ContentNation::activityToJson couldn\'t find user: ' . $receiver);
|
||||
error_log("ContentNation::activityToJson couldn't find user: $receiver");
|
||||
return false;
|
||||
}
|
||||
$targetUrl = $serviceUrl . '/api/profile/' . $localUser->id . '/fedfollow';
|
||||
|
@ -996,8 +970,7 @@ class ContentNation implements Connector
|
|||
case 'dislike':
|
||||
$articleId = \Federator\DIO\Posts::getOriginalArticleId($dbh, $activity);
|
||||
if ($articleId === null) {
|
||||
error_log('ContentNation::activityToJson Failed to get original article ID '
|
||||
. 'for undo vote activity');
|
||||
error_log("ContentNation::activityToJson Failed to get original article ID for undo vote activity");
|
||||
}
|
||||
$activityType = 'vote';
|
||||
$inReplyTo = $object->getInReplyTo();
|
||||
|
@ -1015,8 +988,7 @@ class ContentNation implements Connector
|
|||
}
|
||||
}
|
||||
} else {
|
||||
error_log('ContentNation::activityToJson Unsupported target type for undo '
|
||||
. 'vote with id: ' . $activity->getID() . " Type: " . gettype($target));
|
||||
error_log("ContentNation::activityToJson Unsupported target type for undo vote with id: " . $activity->getID() . " Type: " . gettype($target));
|
||||
return false;
|
||||
}
|
||||
$targetUrl = $serviceUrl . '/api/article/' . $articleId . '/vote';
|
||||
|
@ -1028,17 +1000,16 @@ class ContentNation implements Connector
|
|||
];
|
||||
case 'note':
|
||||
// We don't support comment deletions at this point in time
|
||||
error_log('ContentNation::activityToJson Unsupported undo object type: ' . $objType);
|
||||
error_log("ContentNation::activityToJson Unsupported undo object type: {$objType}");
|
||||
break;
|
||||
default:
|
||||
error_log('ContentNation::activityToJson Unsupported create/update object type: '
|
||||
. $objType);
|
||||
error_log("ContentNation::activityToJson Unsupported create/update object type: {$objType}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
error_log('ContentNation::activityToJson Unsupported activity type: ' . $type);
|
||||
error_log("ContentNation::activityToJson Unsupported activity type: {$type}");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1057,11 +1028,11 @@ class ContentNation implements Connector
|
|||
$signatureHeader = $headers['Signature'] ?? null;
|
||||
|
||||
if (!isset($signatureHeader)) {
|
||||
throw new \Federator\Exceptions\PermissionDenied('Missing Signature header');
|
||||
throw new \Federator\Exceptions\PermissionDenied("Missing Signature header");
|
||||
}
|
||||
|
||||
if (!isset($headers['X-Sender']) || $headers['X-Sender'] !== $this->config['keys']['headerSenderName']) {
|
||||
throw new \Federator\Exceptions\PermissionDenied('Invalid sender name');
|
||||
throw new \Federator\Exceptions\PermissionDenied("Invalid sender name");
|
||||
}
|
||||
|
||||
// Parse Signature header
|
||||
|
@ -1071,11 +1042,11 @@ class ContentNation implements Connector
|
|||
$signature = base64_decode($signatureParts['signature']);
|
||||
$signedHeaders = explode(' ', $signatureParts['headers']);
|
||||
|
||||
$pKeyPath = $_SERVER['DOCUMENT_ROOT'] . '../' . $this->config['keys']['publicKeyPath'];
|
||||
$pKeyPath = PROJECT_ROOT . '/' . $this->config['keys']['publicKeyPath'];
|
||||
$publicKeyPem = file_get_contents($pKeyPath);
|
||||
if ($publicKeyPem === false) {
|
||||
http_response_code(500);
|
||||
throw new \Federator\Exceptions\PermissionDenied('Public key couldn\'t be determined');
|
||||
throw new \Federator\Exceptions\PermissionDenied("Public key couldn't be determined");
|
||||
}
|
||||
|
||||
// Reconstruct the signed string
|
||||
|
@ -1089,7 +1060,7 @@ class ContentNation implements Connector
|
|||
$headerValue = $headers[ucwords($header, '-')] ?? '';
|
||||
}
|
||||
|
||||
$signedString .= strtolower($header) . ': ' . $headerValue . "\n";
|
||||
$signedString .= strtolower($header) . ": " . $headerValue . "\n";
|
||||
}
|
||||
|
||||
$signedString = rtrim($signedString);
|
||||
|
@ -1102,9 +1073,9 @@ class ContentNation implements Connector
|
|||
}
|
||||
if ($verified != 1) {
|
||||
http_response_code(500);
|
||||
throw new \Federator\Exceptions\PermissionDenied('Signature verification failed');
|
||||
throw new \Federator\Exceptions\PermissionDenied("Signature verification failed");
|
||||
}
|
||||
return 'Signature verified.';
|
||||
return "Signature verified.";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1119,5 +1090,6 @@ namespace Federator;
|
|||
function contentnation_load($main)
|
||||
{
|
||||
$cn = new Connector\ContentNation($main);
|
||||
# echo "contentnation::contentnation_load Loaded new connector, adding to main\n"; // TODO change to proper log
|
||||
$main->setConnector($cn);
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ class DummyConnector implements Connector
|
|||
* @param string $userId user id @unused-param
|
||||
* @return \Federator\Data\FedUser[]|false
|
||||
*/
|
||||
public function getFollowersByUser($userId)
|
||||
public function getRemoteFollowersOfUser($userId)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -34,9 +34,10 @@ class DummyConnector implements Connector
|
|||
* get following of given user
|
||||
*
|
||||
* @param string $id user id @unused-param
|
||||
|
||||
* @return \Federator\Data\FedUser[]|false
|
||||
*/
|
||||
public function getFollowingByUser($id)
|
||||
public function getRemoteFollowingForUser($id)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -77,8 +78,7 @@ class DummyConnector implements Connector
|
|||
* (used to identify the article in the remote system) @unused-param
|
||||
* @return \Federator\Data\ActivityPub\Common\Activity|false
|
||||
*/
|
||||
public function jsonToActivity(array $jsonData, &$articleId)
|
||||
{
|
||||
public function jsonToActivity(array $jsonData, &$articleId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -147,5 +147,6 @@ namespace Federator;
|
|||
function dummy_load($main)
|
||||
{
|
||||
$dummy = new Connector\DummyConnector();
|
||||
# echo "dummyconnector::dummy_load Loaded new connector, adding to main\n"; // TODO change to proper log
|
||||
$main->setConnector($dummy);
|
||||
}
|
||||
|
|
|
@ -53,12 +53,11 @@ class RedisCache implements Cache
|
|||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$config = parse_ini_file('../rediscache.ini');
|
||||
$config = parse_ini_file(PROJECT_ROOT . '/rediscache.ini');
|
||||
if ($config !== false) {
|
||||
$this->config = $config;
|
||||
$this->userTTL = array_key_exists('userttl', $config) ? intval($config['userttl'], 10) : 60;
|
||||
$this->publicKeyPemTTL = array_key_exists('publickeypemttl', $config)
|
||||
? intval($config['publickeypemttl'], 10) : 3600;
|
||||
$this->publicKeyPemTTL = array_key_exists('publickeypemttl', $config) ? intval($config['publickeypemttl'], 10) : 3600;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -103,9 +102,9 @@ class RedisCache implements Cache
|
|||
|
||||
* @return \Federator\Data\FedUser[]|false
|
||||
*/
|
||||
public function getFollowersByUser($id)
|
||||
public function getRemoteFollowersOfUser($id)
|
||||
{
|
||||
error_log("rediscache::getFollowersByUser not implemented");
|
||||
error_log("rediscache::getRemoteFollowersOfUser not implemented");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -116,9 +115,9 @@ class RedisCache implements Cache
|
|||
|
||||
* @return \Federator\Data\FedUser[]|false
|
||||
*/
|
||||
public function getFollowingByUser($id)
|
||||
public function getRemoteFollowingForUser($id)
|
||||
{
|
||||
error_log("rediscache::getFollowingByUser not implemented");
|
||||
error_log("rediscache::getRemoteFollowingForUser not implemented");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -254,9 +253,9 @@ class RedisCache implements Cache
|
|||
* @param \Federator\Data\FedUser[]|false $followers user followers @unused-param
|
||||
* @return void
|
||||
*/
|
||||
public function saveFollowersByUser($user, $followers)
|
||||
public function saveRemoteFollowersOfUser($user, $followers)
|
||||
{
|
||||
error_log("rediscache::saveFollowersByUser not implemented");
|
||||
error_log("rediscache::saveRemoteFollowersOfUser not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -266,9 +265,9 @@ class RedisCache implements Cache
|
|||
* @param \Federator\Data\FedUser[]|false $following user following @unused-param
|
||||
* @return void
|
||||
*/
|
||||
public function saveFollowingByUser($user, $following)
|
||||
public function saveRemoteFollowingForUser($user, $following)
|
||||
{
|
||||
error_log("rediscache::saveFollowingByUser not implemented");
|
||||
error_log("rediscache::saveRemoteFollowingForUser not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -301,7 +300,6 @@ class RedisCache implements Cache
|
|||
$serialized = $stats->toJson();
|
||||
$this->redis->setEx($key, $this->config['statsttl'], $serialized);
|
||||
}
|
||||
|
||||
/**
|
||||
* save remote user by name
|
||||
*
|
||||
|
|
|
@ -19,10 +19,10 @@ primary goal is to connect ContentNation via ActivityPub again.
|
|||
- [X] full cache for users
|
||||
- [X] webfinger
|
||||
- [X] discovery endpoints
|
||||
- [X] ap outbox
|
||||
- [X] ap inbox
|
||||
- [ ] ap outbox
|
||||
- [ ] ap inbox
|
||||
- [ ] support for AP profile in service
|
||||
- [ ] support for article
|
||||
- [ ] support for comment
|
||||
- [ ] posting comments from ap to service
|
||||
- [X] callback from service to add new input
|
||||
- [ ] callback from service to add new input
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
create table outbox (`id` char(32) unique primary key, `user` varchar(255), index(user), `timestamp` timestamp, `type` enum ("article", "note"), index(type), `externalid` varchar(255), index(externalid), `apjson` text);
|
||||
update settings set `value`="2024-07-24" where `key`="database_version";
|
|
@ -1,2 +0,0 @@
|
|||
create table webfinger (`subject` varchar(255) unique primary key, `timestamp` timestamp, index(`timestamp`), `aliases` text, `links` text);
|
||||
update settings set `value`="2024-07-22" where `key`="database_version";
|
|
@ -1,3 +0,0 @@
|
|||
create table settings(`key` varchar(255) unique primary key, `value` text);
|
||||
create table users(`id` varchar(255) unique primary key, `externalid` varchar(255), index(`externalid`), `rsapublic` text, `rsaprivate` text);
|
||||
insert into settings (`key`, `value`) value ("database_version", "2024-07-19");
|
|
@ -64,18 +64,18 @@
|
|||
"followers":"https://{$fqdn}/{$username}/followers",
|
||||
"inbox":"https://{$fqdn}/{$username}/inbox",
|
||||
"outbox":"https://{$fqdn}/{$username}/outbox",
|
||||
{*"featured":"https://{$fqdn}/{$username}/collections/featured",
|
||||
"featuredTags":"https://{$fqdn}/{$username}/collections/tags",*}
|
||||
"featured":"https://{$fqdn}/{$username}/collections/featured",
|
||||
"featuredTags":"https://{$fqdn}/{$username}/collections/tags",
|
||||
"preferredUsername":"{$username}",
|
||||
"name":"{$name}",
|
||||
"summary":"{$summary}",
|
||||
"url":"https://{$sourcedomain}/@{$username}",
|
||||
"url":"https://{$fqdn}/@{$username}",
|
||||
"manuallyApprovesFollowers":false,
|
||||
"discoverable":true,
|
||||
"published":"{$registered}",
|
||||
"publicKey":{ldelim}
|
||||
"id":"https://{$fqdn}/{$username}#main-key",
|
||||
"owner":"https://{$sourcedomain}/{$username}",
|
||||
"owner":"https://{$fqdn}/{$username}",
|
||||
"publicKeyPem":"{$publickey}"
|
||||
{rdelim},
|
||||
"tag":[],
|
||||
|
@ -83,7 +83,7 @@
|
|||
{if $type==='group'}{ldelim}
|
||||
"type":"PropertyValue",
|
||||
"name":"website",
|
||||
"value":"\u003ca href=\"https://{$sourcedomain}/@{$username}\" target=\"_blank\" rel=\"nofollow noopener noreferrer me\"\u003e\u003cspan class=\"invisible\"\u003ehttps://\u003c/span\u003e\u003cspan class=\"\"\u003e{$fqdn}/@{$username}\u003c/span\u003e\u003cspan class=\"invisible\"\u003e\u003c/span\u003e\u003c/a\u003e"
|
||||
"value":"\u003ca href=\"https://{$fqdn}/@{$username}\" target=\"_blank\" rel=\"nofollow noopener noreferrer me\"\u003e\u003cspan class=\"invisible\"\u003ehttps://\u003c/span\u003e\u003cspan class=\"\"\u003e{$fqdn}/@{$username}\u003c/span\u003e\u003cspan class=\"invisible\"\u003e\u003c/span\u003e\u003c/a\u003e"
|
||||
{rdelim}{/if}
|
||||
],
|
||||
"endpoints":{ldelim}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{ldelim}
|
||||
"subject": "acct:{$username}@{$sourcedomain}",
|
||||
"subject": "acct:{$username}@{$domain}",
|
||||
"aliases": [
|
||||
"https://{$sourcedomain}/@{$username}"
|
||||
"https://{$domain}/@{$username}"
|
||||
],
|
||||
"links": [
|
||||
{ldelim}"rel": "self", "type": "application/activity+json", "href": "https://{$domain}/{$username}"{rdelim},
|
||||
|
|
Loading…
Add table
Reference in a new issue