revert to single-connector support

- removed support for multiple connector-plugins
- fixed issue where outbox wasn't properly retrieved from contentnation
This commit is contained in:
Yannis Vogel 2025-04-10 09:02:45 +02:00
parent 1a7b8264a1
commit 721e37882d
No known key found for this signature in database
18 changed files with 64 additions and 132 deletions

View file

@ -14,7 +14,7 @@
<div id="request-container"> <div id="request-container">
<!-- Request Form Template --> <!-- Request Form Template -->
<div class="request-box border p-4 rounded-lg mb-4 bg-gray-50"> <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> <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" <input type="text" class="target-link-input w-full p-2 border rounded-md mb-2"
placeholder="Enter target link" value="federator/fedusers/grumpydevelop/outbox?page=0"> placeholder="Enter target link" value="federator/fedusers/grumpydevelop/outbox?page=0">
@ -38,7 +38,7 @@
</div> </div>
<p class="mt-2 text-sm text-gray-700">Response:</p> <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">Waiting for response</p> <p class="response mt-2 text-sm text-gray-700 font-mono whitespace-pre-wrap">Waiting for response</p>
</div> </div>
</div> </div>
@ -94,11 +94,11 @@
const container = document.getElementById("request-container"); const container = document.getElementById("request-container");
const requestBox = container.firstElementChild.cloneNode(true); const requestBox = container.firstElementChild.cloneNode(true);
requestBox.querySelector(".target-link-input").value = "federator/v1/dummy/moo"; requestBox.querySelector(".target-link-input").value = "federator/fedusers/grumpydevelop@contentnation.net/outbox?page=0";
requestBox.querySelector(".request-type-input").value = "GET"; requestBox.querySelector(".request-type-input").value = "GET";
requestBox.querySelector(".session-input").value = "somethingvalider"; requestBox.querySelector(".session-input").value = "";
requestBox.querySelector(".profile-input").value = "ihaveone"; requestBox.querySelector(".profile-input").value = "";
requestBox.querySelector(".response").textContent = ""; requestBox.querySelector(".response").textContent = "Waiting for response";
requestBox.querySelector(".send-btn").addEventListener("click", function () { requestBox.querySelector(".send-btn").addEventListener("click", function () {
sendRequest(this); sendRequest(this);

View file

@ -75,20 +75,9 @@ class Api extends Main
$this->openDatabase(); $this->openDatabase();
$this->loadPlugins(); $this->loadPlugins();
$host = 'dummy'; // fallback
// Check if path matches something like fedusers/username@domain.tld
if (preg_match("#^fedusers/([^@]+)@([^/]+)/.*$#", $this->path, $matches) === 1) {
$host = strtolower($matches[2]); // extract domain
} else {
$host = 'dummy';
echo "using dummy host in API::run\n";
}
$connector = $this->getConnectorForHost($host);
$retval = ""; $retval = "";
$handler = null; $handler = null;
if ($connector === null) { if ($this->connector === null) {
http_response_code(500); http_response_code(500);
return; return;
} }
@ -97,7 +86,7 @@ class Api extends Main
$this->dbh, $this->dbh,
$_SERVER['HTTP_X_SESSION'], $_SERVER['HTTP_X_SESSION'],
$_SERVER['HTTP_X_PROFILE'], $_SERVER['HTTP_X_PROFILE'],
$connector, $this->connector,
$this->cache $this->cache
); );
if ($this->user === false) { if ($this->user === false) {
@ -124,7 +113,7 @@ class Api extends Main
$printresponse = true; $printresponse = true;
if ($handler !== null) { if ($handler !== null) {
try { try {
$printresponse = $handler->exec($this->paths, $this->user, $connector); $printresponse = $handler->exec($this->paths, $this->user);
if ($printresponse) { if ($printresponse) {
$retval = $handler->toJson(); $retval = $handler->toJson();
} }

View file

@ -19,10 +19,9 @@ interface APIInterface
* @param array<string> $paths path array split by / * @param array<string> $paths path array split by /
* *
* @param \Federator\Data\User|false $user user who is calling us * @param \Federator\Data\User|false $user user who is calling us
* @param \Federator\Connector\Connector $connector connector to use
* @return bool true on success * @return bool true on success
*/ */
public function exec($paths, $user, $connector); public function exec($paths, $user);
/** /**
* get internal represenation as json string * get internal represenation as json string

View file

@ -43,10 +43,9 @@ class FedUsers implements APIInterface
* *
* @param array<string> $paths path array split by / * @param array<string> $paths path array split by /
* @param \Federator\Data\User|false $user user who is calling us @unused-param * @param \Federator\Data\User|false $user user who is calling us @unused-param
* @param \Federator\Connector\Connector $connector connector to use
* @return bool true on success * @return bool true on success
*/ */
public function exec($paths, $user, $connector) public function exec($paths, $user)
{ {
$method = $_SERVER["REQUEST_METHOD"]; $method = $_SERVER["REQUEST_METHOD"];
$handler = null; $handler = null;
@ -54,7 +53,7 @@ class FedUsers implements APIInterface
case 2: case 2:
if ($method === 'GET') { if ($method === 'GET') {
// /users/username or /@username // /users/username or /@username
return $this->returnUserProfile($paths[1], $connector); return $this->returnUserProfile($paths[1]);
} }
break; break;
case 3: case 3:
@ -83,10 +82,10 @@ class FedUsers implements APIInterface
$ret = false; $ret = false;
switch ($method) { switch ($method) {
case 'GET': case 'GET':
$ret = $handler->get($paths[1], $connector); $ret = $handler->get($paths[1]);
break; break;
case 'POST': case 'POST':
$ret = $handler->post($paths[1], $connector); $ret = $handler->post($paths[1]);
break; break;
} }
if ($ret !== false) { if ($ret !== false) {
@ -103,15 +102,14 @@ class FedUsers implements APIInterface
* return user profile * return user profile
* *
* @param string $_name * @param string $_name
* @param \Federator\Connector\Connector $connector connector to use
* @return boolean true on success * @return boolean true on success
*/ */
private function returnUserProfile($_name, $connector) private function returnUserProfile($_name)
{ {
$user = \Federator\DIO\User::getUserByName( $user = \Federator\DIO\User::getUserByName(
$this->main->getDatabase(), $this->main->getDatabase(),
$_name, $_name,
$connector, $this->main->getConnector(),
$this->main->getCache() $this->main->getCache()
); );
if ($user === false || $user->id === null) { if ($user === false || $user->id === null) {

View file

@ -14,17 +14,15 @@ interface FedUsersInterface
* get call for user * get call for user
* *
* @param string $_user user to fetch data for * @param string $_user user to fetch data for
* @param \Federator\Connector\Connector $connector connector to use
* @return string|false response or false in case of error * @return string|false response or false in case of error
*/ */
public function get($_user, $connector); public function get($_user);
/** /**
* post call for user * post call for user
* *
* @param string $_user user to add data to * @param string $_user user to add data to
* @param \Federator\Connector\Connector $connector connector to use
* @return string|false response or false in case of error * @return string|false response or false in case of error
*/ */
public function post($_user, $connector); public function post($_user);
} }

View file

@ -33,13 +33,13 @@ class Outbox implements \Federator\Api\FedUsers\FedUsersInterface
* handle get call * handle get call
* *
* @param string $_user user to fetch outbox for * @param string $_user user to fetch outbox for
* @param \Federator\Connector\Connector $connector connector to use
* @return string|false response * @return string|false response
*/ */
public function get($_user, $connector) public function get($_user)
{ {
$dbh = $this->main->getDatabase(); $dbh = $this->main->getDatabase();
$cache = $this->main->getCache(); $cache = $this->main->getCache();
$connector = $this->main->getConnector();
// get user // get user
$user = \Federator\DIO\User::getUserByName( $user = \Federator\DIO\User::getUserByName(
@ -84,10 +84,9 @@ class Outbox implements \Federator\Api\FedUsers\FedUsersInterface
* handle post call * handle post call
* *
* @param string $_user user to add data to outbox @unused-param * @param string $_user user to add data to outbox @unused-param
* @param \Federator\Connector\Connector $connector connector to use
* @return string|false response * @return string|false response
*/ */
public function post($_user, $connector) public function post($_user)
{ {
return false; return false;
} }

View file

@ -42,10 +42,9 @@ class Dummy implements \Federator\Api\APIInterface
* *
* @param array<string> $paths path array split by / * @param array<string> $paths path array split by /
* @param \Federator\Data\User|false $user user who is calling us * @param \Federator\Data\User|false $user user who is calling us
* @param \Federator\Connector\Connector $connector connector to use
* @return bool true on success * @return bool true on success
*/ */
public function exec($paths, $user, $connector) : bool public function exec($paths, $user) : bool
{ {
// only for user with the 'publish' permission // only for user with the 'publish' permission
if ($user === false || $user->hasPermission('publish') === false) { if ($user === false || $user->hasPermission('publish') === false) {

View file

@ -57,7 +57,7 @@ class WellKnown implements APIInterface
* @param \Federator\Data\User|false $user user who is calling us @unused-param * @param \Federator\Data\User|false $user user who is calling us @unused-param
* @return bool true on success * @return bool true on success
*/ */
public function exec($paths, $user, $connector) public function exec($paths, $user)
{ {
$method = $_SERVER["REQUEST_METHOD"]; $method = $_SERVER["REQUEST_METHOD"];
switch ($method) { switch ($method) {
@ -66,14 +66,14 @@ class WellKnown implements APIInterface
case 2: case 2:
if ($paths[0] === 'nodeinfo') { if ($paths[0] === 'nodeinfo') {
$ni = new WellKnown\NodeInfo($this, $this->main); $ni = new WellKnown\NodeInfo($this, $this->main);
return $ni->exec($paths, $connector); return $ni->exec($paths);
} }
switch ($paths[1]) { switch ($paths[1]) {
case 'host-meta': case 'host-meta':
return $this->hostMeta(); return $this->hostMeta();
case 'nodeinfo': case 'nodeinfo':
$ni = new WellKnown\NodeInfo($this, $this->main); $ni = new WellKnown\NodeInfo($this, $this->main);
return $ni->exec($paths, $connector); return $ni->exec($paths);
case 'webfinger': case 'webfinger':
$wf = new WellKnown\WebFinger($this, $this->main); $wf = new WellKnown\WebFinger($this, $this->main);
return $wf->exec(); return $wf->exec();

View file

@ -41,10 +41,9 @@ class NodeInfo
* handle nodeinfo request * handle nodeinfo request
* *
* @param string[] $paths path of us * @param string[] $paths path of us
* @param \Federator\Connector\Connector $connector connector to use
* @return bool true on success * @return bool true on success
*/ */
public function exec($paths, $connector) public function exec($paths)
{ {
$data = [ $data = [
'fqdn' => $_SERVER['SERVER_NAME'] 'fqdn' => $_SERVER['SERVER_NAME']
@ -65,7 +64,7 @@ class NodeInfo
default: default:
$template = 'nodeinfo2.0.json'; $template = 'nodeinfo2.0.json';
} }
$stats = \Federator\DIO\Stats::getStats($this->main, $connector); $stats = \Federator\DIO\Stats::getStats($this->main);
echo "fetch usercount via connector\n"; echo "fetch usercount via connector\n";
$data['usercount'] = $stats->userCount; $data['usercount'] = $stats->userCount;
$data['postcount'] = $stats->postCount; $data['postcount'] = $stats->postCount;

View file

@ -53,7 +53,7 @@ class WebFinger
$user = \Federator\DIO\User::getUserByName( $user = \Federator\DIO\User::getUserByName(
$this->main->getDatabase(), $this->main->getDatabase(),
$matches[1], $matches[1],
$this->main->getConnectorForHost($domain), $this->main->getConnector(),
$this->main->getCache() $this->main->getCache()
); );
if ($user->id == 0) { if ($user->id == 0) {

View file

@ -13,12 +13,6 @@ namespace Federator\Connector;
*/ */
interface Connector interface Connector
{ {
/**
* get the host this connector is dedicated to
*
* @return string
*/
public function getHost();
/** /**
* get posts by given user * get posts by given user
* *

View file

@ -18,10 +18,9 @@ class Stats
* get remote stats * get remote stats
* *
* @param \Federator\Main $main main instance * @param \Federator\Main $main main instance
* @param \Federator\Connector\Connector $connector connector to use
* @return \Federator\Data\Stats * @return \Federator\Data\Stats
*/ */
public static function getStats($main, $connector) public static function getStats($main)
{ {
$cache = $main->getCache(); $cache = $main->getCache();
// ask cache // ask cache
@ -31,6 +30,7 @@ class Stats
return $stats; return $stats;
} }
} }
$connector = $main->getConnector();
// ask connector for stats // ask connector for stats
$stats = $connector->getRemoteStats(); $stats = $connector->getRemoteStats();
if ($cache !== null && $stats !== false) { if ($cache !== null && $stats !== false) {

View file

@ -192,7 +192,7 @@ class User
if ($user->id === null && $user->externalid !== null) { if ($user->id === null && $user->externalid !== null) {
self::addLocalUser($dbh, $user, $_name); self::addLocalUser($dbh, $user, $_name);
} }
$cache->saveRemoteUserByName($_name, user: $user); $cache->saveRemoteUserByName($_name, $user);
} }
return $user; return $user;
} }

View file

@ -28,11 +28,11 @@ class Main
*/ */
protected $config; protected $config;
/** /**
* remote connectors * remote connector
* *
* @var array<string, Connector\Connector> $connectors * @var Connector\Connector $connector
*/ */
protected $connectors = []; protected $connector = null;
/** /**
* response content type * response content type
* *
@ -139,17 +139,14 @@ class Main
{ {
return $this->cache; return $this->cache;
} }
/** /**
* Get the connector for a given remote host * get connector
* *
* @param string $remoteHost The host from the actor URL (e.g. mastodon.social) * @return \Federator\Connector\Connector
* @return Connector\Connector|null */
*/ public function getConnector()
public function getConnectorForHost(string $remoteHost): ?Connector\Connector
{ {
$host = strtolower(parse_url($remoteHost, PHP_URL_HOST) ?? $remoteHost); return $this->connector;
return $this->connectors[$host] ?? null;
} }
/** /**
@ -234,6 +231,8 @@ class Main
/** /**
* set cache * set cache
*
* @param \Federator\Cache\Cache $cache the new cache
*/ */
public function setCache(Cache\Cache $cache): void public function setCache(Cache\Cache $cache): void
{ {
@ -241,16 +240,16 @@ class Main
} }
/** /**
* Set a connector for a specific remote host * set connector
* *
* @param string $remoteURL The remote host (like mastodon.social or contentnation.net) * @param \Federator\Connector\Connector $connector the new connector
* @param Connector\Connector $connector The connector instance
*/ */
public function addConnector(string $remoteURL, Connector\Connector $connector): void public function setConnector(Connector\Connector $connector) : void
{ {
// Normalize the host (no scheme, lowercase) if ($this->connector) {
$host = strtolower(parse_url($remoteURL, PHP_URL_HOST) ?? $remoteURL); echo "main::setConnector Setting new connector will override old one.\n"; // TODO CHANGE TO LOG WARNING
$this->connectors[$host] = $connector; }
$this->connector = $connector;
} }
/** /**

View file

@ -49,15 +49,6 @@ class ContentNation implements Connector
$this->main = $main; $this->main = $main;
} }
/**
* get the host this connector is dedicated to
*
* @return string
*/
public function getHost() {
return "contentnation.net";
}
/** /**
* get posts by given user * get posts by given user
* *
@ -68,7 +59,10 @@ class ContentNation implements Connector
*/ */
public function getRemotePostsByUser($userId, $min, $max) public function getRemotePostsByUser($userId, $min, $max)
{ {
$remoteURL = $this->service . '/api/profile/' . $userId . '/activities'; if (preg_match("#^([^@]+)@([^/]+)#", $userId, $matches) === 1) {
$userId = $matches[1];
}
$remoteURL = $this->service . '/api/profile/' . urlencode($userId) . '/activities';
if ($min !== '') { if ($min !== '') {
$remoteURL .= '&minTS=' . urlencode($min); $remoteURL .= '&minTS=' . urlencode($min);
} }
@ -133,15 +127,16 @@ class ContentNation implements Connector
$apArticle->setID($idurl) $apArticle->setID($idurl)
->setURL($idurl); ->setURL($idurl);
$image = $activity['image'] ?? $activity['profileimg']; $image = $activity['image'] ?? $activity['profileimg'];
// $mediaType = @mime_content_type($imgpath . $activity['profile'] . '/' . $image) | 'text/plain'; // old approach, using local copy of images $path = $imgpath . $activity['profile'] . '/' . $image;
$imgUrl = $userdata . '/' . $activity['profile'] . $image; $mediaType = (file_exists($path) && ($type = mime_content_type($path)) && !str_starts_with($type, 'text/'))
$mediaType = $this->getRemoteMimeType($imgUrl) ?? 'text/plain'; ? $type
: 'image/jpeg';
$img = new \Federator\Data\ActivityPub\Common\Image(); $img = new \Federator\Data\ActivityPub\Common\Image();
$img->setMediaType($mediaType) $img->setMediaType($mediaType)
->setName($articleimage) ->setName($articleimage)
->setURL($userdata . '/' . $activity['profile'] . $image); ->setURL($userdata . '/' . $activity['profile'] . $image);
$apArticle->addImage($img); $apArticle->addImage($img);
$create->setObject(object: $apArticle); $create->setObject($apArticle);
$posts[] = $create; $posts[] = $create;
break; // Article break; // Article
case 'Comment': case 'Comment':
@ -200,18 +195,6 @@ class ContentNation implements Connector
} }
return $posts; return $posts;
} }
/**
* Get the MIME type of a remote file by its URL.
*
* @param string $_url The URL of the remote file.
* @return string|false The MIME type if found, or false on failure.
*/
public function getRemoteMimeType($url)
{
$headers = get_headers($url, 1);
return $headers['Content-Type'] ?? 'unknown';
}
/** /**
* get statistics from remote system * get statistics from remote system
@ -326,5 +309,6 @@ namespace Federator;
function contentnation_load($main) function contentnation_load($main)
{ {
$cn = new Connector\ContentNation($main); $cn = new Connector\ContentNation($main);
$main->addConnector($cn->getHost(), $cn); echo "contentnation::contentnation_load Loaded new connector, adding to main\n"; // TODO change to proper log
$main->setConnector($cn);
} }

View file

@ -18,15 +18,6 @@ class DummyConnector implements Connector
public function __construct() public function __construct()
{ {
} }
/**
* get the host this connector is dedicated to
*
* @return string
*/
public function getHost() {
return "dummy";
}
/** /**
* get posts by given user * get posts by given user
@ -96,5 +87,6 @@ namespace Federator;
function dummy_load($main) function dummy_load($main)
{ {
$dummy = new Connector\DummyConnector(); $dummy = new Connector\DummyConnector();
$main->addConnector($dummy->getHost(), $dummy); echo "dummyconnector::dummy_load Loaded new connector, adding to main\n"; // TODO change to proper log
$main->setConnector($dummy);
} }

View file

@ -49,16 +49,6 @@ class Mastodon implements Connector
$this->main = $main; $this->main = $main;
} }
/**
* get the host this connector is dedicated to
*
* @return string
*/
public function getHost()
{
return "mastodon.social";
}
/** /**
* get posts by given user * get posts by given user
* *
@ -363,5 +353,6 @@ namespace Federator;
function mastodon_load($main) function mastodon_load($main)
{ {
$mast = new Connector\Mastodon($main); $mast = new Connector\Mastodon($main);
$main->addConnector($mast->getHost(), $mast); echo "mastodon::mastodon_load Loaded new connector, adding to main\n"; // TODO change to proper log
$main->setConnector($mast);
} }

View file

@ -53,15 +53,6 @@ class RedisCache implements Cache
} }
} }
/**
* get the host this connector is dedicated to
*
* @return string
*/
public function getHost() {
return "redis";
}
/** /**
* connect to redis * connect to redis
* @return void * @return void