forked from grumpydevelop/federator
Compare commits
No commits in common. "097f871ed6af93becc16576ca60bbc1ced1b36d5" and "207d876254622aaf6018e2150abb8e1cfb2b25d8" have entirely different histories.
097f871ed6
...
207d876254
19 changed files with 97 additions and 381 deletions
|
@ -87,7 +87,7 @@ class Followers implements \Federator\Api\FedUsers\FedUsersInterface
|
||||||
// Pagination navigation
|
// Pagination navigation
|
||||||
$lastPage = max(0, ceil($totalItems / $pageSize) - 1);
|
$lastPage = max(0, ceil($totalItems / $pageSize) - 1);
|
||||||
|
|
||||||
if ($page === "" || $followers->getCount() == 0) {
|
if ($page === "" || $followers->count() == 0) {
|
||||||
$followers->setFirst($baseUrl . '?page=0');
|
$followers->setFirst($baseUrl . '?page=0');
|
||||||
$followers->setLast($baseUrl . '?page=' . $lastPage);
|
$followers->setLast($baseUrl . '?page=' . $lastPage);
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,7 +87,7 @@ class Following implements \Federator\Api\FedUsers\FedUsersInterface
|
||||||
// Pagination navigation
|
// Pagination navigation
|
||||||
$lastPage = max(0, ceil($totalItems / $pageSize) - 1);
|
$lastPage = max(0, ceil($totalItems / $pageSize) - 1);
|
||||||
|
|
||||||
if ($page === "" || $following->getCount() == 0) {
|
if ($page === "" || $following->count() == 0) {
|
||||||
$following->setFirst($baseUrl . '?page=0');
|
$following->setFirst($baseUrl . '?page=0');
|
||||||
$following->setLast($baseUrl . '?page=' . $lastPage);
|
$following->setLast($baseUrl . '?page=' . $lastPage);
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,33 +56,34 @@ class Outbox implements \Federator\Api\FedUsers\FedUsersInterface
|
||||||
|
|
||||||
// get posts from user
|
// get posts from user
|
||||||
$outbox = new \Federator\Data\ActivityPub\Common\Outbox();
|
$outbox = new \Federator\Data\ActivityPub\Common\Outbox();
|
||||||
$min = intval($this->main->extractFromURI('min', '0'), 10);
|
$min = $this->main->extractFromURI("min", "");
|
||||||
$max = intval($this->main->extractFromURI('max', '0'), 10);
|
$max = $this->main->extractFromURI("max", "");
|
||||||
$page = $this->main->extractFromURI("page", "");
|
$page = $this->main->extractFromURI("page", "");
|
||||||
if ($page !== "") {
|
if ($page !== "") {
|
||||||
$items = \Federator\DIO\Posts::getPostsByUser($dbh, $user->id, $connector, $cache, $min, $max, 20);
|
$items = \Federator\DIO\Posts::getPostsByUser($dbh, $user->id, $connector, $cache, $min, $max);
|
||||||
$outbox->setItems($items);
|
$outbox->setItems($items);
|
||||||
} else {
|
} else {
|
||||||
$tmpitems = \Federator\DIO\Posts::getPostsByUser($dbh, $user->id, $connector, $cache, $min, $max, 99999);
|
|
||||||
$outbox->setTotalItems(sizeof($tmpitems));
|
|
||||||
$items = [];
|
$items = [];
|
||||||
}
|
}
|
||||||
$config = $this->main->getConfig();
|
$config = $this->main->getConfig();
|
||||||
$domain = $config['generic']['externaldomain'];
|
$domain = $config['generic']['externaldomain'];
|
||||||
$id = 'https://' . $domain . '/' . $_user . '/outbox';
|
$id = 'https://' . $domain . '/users/' . $_user . '/outbox';
|
||||||
$outbox->setPartOf($id);
|
$outbox->setPartOf($id);
|
||||||
$outbox->setID($id);
|
$outbox->setID($id);
|
||||||
if ($page === '') {
|
if ($page !== '') {
|
||||||
|
$id .= '?page=' . urlencode($page);
|
||||||
|
} else {
|
||||||
$outbox->setType('OrderedCollection');
|
$outbox->setType('OrderedCollection');
|
||||||
}
|
}
|
||||||
if ($page === '' || $outbox->getCount() == 0) {
|
if ($page === '' || $outbox->count() == 0) {
|
||||||
$outbox->setFirst($id . '?page=true');
|
$outbox->setFirst($id . '?page=0');
|
||||||
|
$outbox->setLast($id . '&min=0');
|
||||||
}
|
}
|
||||||
if (sizeof($items) > 0) {
|
if (sizeof($items) > 0) {
|
||||||
$oldestTS = $items[0]->getPublished();
|
$newestId = $items[0]->getPublished();
|
||||||
$newestTS = $items[sizeof($items) - 1]->getPublished();
|
$oldestId = $items[sizeof($items) - 1]->getPublished();
|
||||||
$outbox->setNext($id . '?page=true&max=' . $newestTS);
|
$outbox->setNext($id . '&max=' . $newestId);
|
||||||
$outbox->setPrev($id . '?page=true&min=' . $oldestTS);
|
$outbox->setPrev($id . '&min=' . $oldestId);
|
||||||
}
|
}
|
||||||
$obj = $outbox->toObject();
|
$obj = $outbox->toObject();
|
||||||
return json_encode($obj, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
|
return json_encode($obj, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
|
||||||
|
|
5
php/federator/cache/cache.php
vendored
5
php/federator/cache/cache.php
vendored
|
@ -35,13 +35,10 @@ interface Cache extends \Federator\Connector\Connector
|
||||||
* save remote posts by user
|
* save remote posts by user
|
||||||
*
|
*
|
||||||
* @param string $user user name
|
* @param string $user user name
|
||||||
* @param int $min min timestamp
|
|
||||||
* @param int $max max timestamp
|
|
||||||
* @param int $limit limit results
|
|
||||||
* @param \Federator\Data\ActivityPub\Common\APObject[]|false $posts user posts
|
* @param \Federator\Data\ActivityPub\Common\APObject[]|false $posts user posts
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function saveRemotePostsByUser($user, $min, $max, $limit, $posts);
|
public function saveRemotePostsByUser($user, $posts);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* save remote stats
|
* save remote stats
|
||||||
|
|
|
@ -35,13 +35,12 @@ interface Connector
|
||||||
* get posts by given user
|
* get posts by given user
|
||||||
*
|
*
|
||||||
* @param string $id user id
|
* @param string $id user id
|
||||||
* @param int $min min value
|
* @param string $min min date
|
||||||
* @param int $max max value
|
* @param string $max max date
|
||||||
* @param int $limit maximum number of results
|
|
||||||
|
|
||||||
* @return \Federator\Data\ActivityPub\Common\Activity[]|false
|
* @return \Federator\Data\ActivityPub\Common\Activity[]|false
|
||||||
*/
|
*/
|
||||||
public function getRemotePostsByUser($id, $min, $max, $limit);
|
public function getRemotePostsByUser($id, $min, $max);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get remote user by given name
|
* get remote user by given name
|
||||||
|
|
|
@ -48,20 +48,7 @@ class Collection extends APObject
|
||||||
*/
|
*/
|
||||||
public function fromJson($json)
|
public function fromJson($json)
|
||||||
{
|
{
|
||||||
$success = parent::fromJson($json);
|
return parent::fromJson($json);
|
||||||
if (!$success) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (array_key_exists('totalItems', $json)) {
|
|
||||||
$this->totalItems = $json['totalItems'];
|
|
||||||
}
|
|
||||||
if (array_key_exists('first', $json)) {
|
|
||||||
$this->first = $json['first'];
|
|
||||||
}
|
|
||||||
if (array_key_exists('last', $json)) {
|
|
||||||
$this->last = $json['last'];
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -74,16 +61,11 @@ class Collection extends APObject
|
||||||
$this->totalItems = $totalItems;
|
$this->totalItems = $totalItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTotalItems(): int
|
public function count(): int
|
||||||
{
|
{
|
||||||
return $this->totalItems;
|
return $this->totalItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getFirst(): string
|
|
||||||
{
|
|
||||||
return $this->first;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setFirst(string $url): void
|
public function setFirst(string $url): void
|
||||||
{
|
{
|
||||||
$this->first = $url;
|
$this->first = $url;
|
||||||
|
|
|
@ -25,7 +25,7 @@ class Followers extends OrderedCollectionPage
|
||||||
public function setItems(&$items)
|
public function setItems(&$items)
|
||||||
{
|
{
|
||||||
// Optionally: type check that all $items are Activity objects
|
// Optionally: type check that all $items are Activity objects
|
||||||
$this->orderedItems = $items;
|
$this->items = $items;
|
||||||
$this->totalItems = sizeof($items);
|
$this->totalItems = sizeof($items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ class Following extends OrderedCollectionPage
|
||||||
public function setItems(&$items)
|
public function setItems(&$items)
|
||||||
{
|
{
|
||||||
// Optionally: type check that all $items are Activity objects
|
// Optionally: type check that all $items are Activity objects
|
||||||
$this->orderedItems = $items;
|
$this->items = $items;
|
||||||
$this->totalItems = sizeof($items);
|
$this->totalItems = sizeof($items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ class OrderedCollection extends Collection
|
||||||
*
|
*
|
||||||
* @var APObject[]|string[]
|
* @var APObject[]|string[]
|
||||||
*/
|
*/
|
||||||
protected $orderedItems = [];
|
protected $items = [];
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
|
@ -32,8 +32,8 @@ class OrderedCollection extends Collection
|
||||||
{
|
{
|
||||||
$return = parent::toObject();
|
$return = parent::toObject();
|
||||||
$return['type'] = 'OrderedCollection';
|
$return['type'] = 'OrderedCollection';
|
||||||
if (sizeof($this->orderedItems) > 0) {
|
if ($this->totalItems > 0) {
|
||||||
foreach ($this->orderedItems as $item) {
|
foreach ($this->items as $item) {
|
||||||
if (is_string($item)) {
|
if (is_string($item)) {
|
||||||
$return['orderedItems'][] = $item;
|
$return['orderedItems'][] = $item;
|
||||||
} elseif (is_object($item)) {
|
} elseif (is_object($item)) {
|
||||||
|
@ -52,20 +52,7 @@ class OrderedCollection extends Collection
|
||||||
*/
|
*/
|
||||||
public function fromJson($json)
|
public function fromJson($json)
|
||||||
{
|
{
|
||||||
$success = parent::fromJson($json);
|
return parent::fromJson($json);
|
||||||
if (!$success) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (array_key_exists('orderedItems', $json)) {
|
|
||||||
foreach ($json['orderedItems'] as $item) {
|
|
||||||
$obj = \Federator\Data\ActivityPub\Factory::newActivityFromJson($item);
|
|
||||||
if ($obj !== false) {
|
|
||||||
$this->orderedItems[] = $obj;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -74,7 +61,8 @@ class OrderedCollection extends Collection
|
||||||
*/
|
*/
|
||||||
public function append(&$item): void
|
public function append(&$item): void
|
||||||
{
|
{
|
||||||
$this->orderedItems[] = $item;
|
$this->items[] = $item;
|
||||||
|
$this->totalItems = sizeof($this->items);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -85,22 +73,18 @@ class OrderedCollection extends Collection
|
||||||
public function get(int $index)
|
public function get(int $index)
|
||||||
{
|
{
|
||||||
if ($index >= 0) {
|
if ($index >= 0) {
|
||||||
if ($index >= sizeof($this->orderedItems)) {
|
if ($index >= $this->totalItems) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return $this->orderedItems[$index];
|
return $this->items[$index];
|
||||||
} else {
|
} else {
|
||||||
if (sizeof($this->orderedItems) + $index < 0) {
|
if ($this->totalItems + $index < 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return $this->orderedItems[sizeof($this->orderedItems) + $index];
|
return $this->items[$this->totalItems + $index];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getCount(): int
|
|
||||||
{
|
|
||||||
return sizeof($this->orderedItems);
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
* set items
|
* set items
|
||||||
*
|
*
|
||||||
|
@ -109,6 +93,7 @@ class OrderedCollection extends Collection
|
||||||
*/
|
*/
|
||||||
public function setItems(&$items)
|
public function setItems(&$items)
|
||||||
{
|
{
|
||||||
$this->orderedItems = $items;
|
$this->items = $items;
|
||||||
|
$this->totalItems = sizeof($items);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,29 +51,7 @@ class OrderedCollectionPage extends OrderedCollection
|
||||||
*/
|
*/
|
||||||
public function fromJson($json)
|
public function fromJson($json)
|
||||||
{
|
{
|
||||||
$success = parent::fromJson($json);
|
return parent::fromJson($json);
|
||||||
if (!$success) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (array_key_exists('next', $json)) {
|
|
||||||
$this->next = $json['next'];
|
|
||||||
}
|
|
||||||
if (array_key_exists('prev', $json)) {
|
|
||||||
$this->prev = $json['prev'];
|
|
||||||
}
|
|
||||||
if (array_key_exists('partOf', $json)) {
|
|
||||||
$this->partOf = $json['partOf'];
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get next url
|
|
||||||
* @return string next URL
|
|
||||||
*/
|
|
||||||
public function getNext()
|
|
||||||
{
|
|
||||||
return $this->next;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -35,7 +35,8 @@ class Outbox extends OrderedCollectionPage
|
||||||
public function setItems(&$items)
|
public function setItems(&$items)
|
||||||
{
|
{
|
||||||
// Optionally: type check that all $items are Activity objects
|
// Optionally: type check that all $items are Activity objects
|
||||||
$this->orderedItems = $items;
|
$this->items = $items;
|
||||||
|
$this->totalItems = sizeof($items);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
Array
|
|
||||||
(
|
|
||||||
[type] => Document
|
|
||||||
[mediaType] => image/jpeg
|
|
||||||
[url] => https://noc.social/system/media_attachments/files/112/350/286/131/419/396/original/26ab9c8a4ab13f16.jpg
|
|
||||||
[name] => Screenshot Zeitleiste aus kdenlive.
|
|
||||||
[blurhash] => UC8|^tSwI-bu-taeRiaeu5e.aJjGsBWnR*jH
|
|
||||||
[focalPoint] => Array
|
|
||||||
(
|
|
||||||
[0] => -0.01
|
|
||||||
[1] => -0.79
|
|
||||||
)
|
|
||||||
|
|
||||||
[width] => 1333
|
|
||||||
[height] => 651
|
|
||||||
)
|
|
|
@ -16,7 +16,7 @@ class Factory
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* create object tree from json
|
* create object tree from json
|
||||||
* @param array<string, mixed>|mixed $json input json
|
* @param array<string, mixed> $json input json
|
||||||
* @return Common\APObject|null object or false on error
|
* @return Common\APObject|null object or false on error
|
||||||
*/
|
*/
|
||||||
public static function newFromJson($json, string $jsonstring)
|
public static function newFromJson($json, string $jsonstring)
|
||||||
|
@ -67,12 +67,6 @@ class Factory
|
||||||
case 'Inbox':
|
case 'Inbox':
|
||||||
$return = new Common\Inbox();
|
$return = new Common\Inbox();
|
||||||
break;
|
break;
|
||||||
case 'OrderedCollection':
|
|
||||||
$return = new Common\OrderedCollection();
|
|
||||||
break;
|
|
||||||
case 'OrderedCollectionPage':
|
|
||||||
$return = new Common\OrderedCollectionPage();
|
|
||||||
break;
|
|
||||||
case 'Tombstone':
|
case 'Tombstone':
|
||||||
$return = new Common\APObject("Tombstone");
|
$return = new Common\APObject("Tombstone");
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -24,24 +24,22 @@ class Posts
|
||||||
* connector to fetch use with
|
* connector to fetch use with
|
||||||
* @param \Federator\Cache\Cache|null $cache
|
* @param \Federator\Cache\Cache|null $cache
|
||||||
* optional caching service
|
* optional caching service
|
||||||
* @param int $min
|
* @param string $min
|
||||||
* minimum timestamp
|
* minimum date
|
||||||
* @param int $max
|
* @param string $max
|
||||||
* maximum timestamp
|
* maximum date
|
||||||
* @param int $limit
|
|
||||||
* maximum number of results
|
|
||||||
* @return \Federator\Data\ActivityPub\Common\Activity[]
|
* @return \Federator\Data\ActivityPub\Common\Activity[]
|
||||||
*/
|
*/
|
||||||
public static function getPostsByUser($dbh, $userid, $connector, $cache, $min, $max, $limit)
|
public static function getPostsByUser($dbh, $userid, $connector, $cache, $min, $max)
|
||||||
{
|
{
|
||||||
// ask cache
|
// ask cache
|
||||||
if ($cache !== null) {
|
if ($cache !== null) {
|
||||||
$posts = $cache->getRemotePostsByUser($userid, $min, $max, $limit);
|
$posts = $cache->getRemotePostsByUser($userid, $min, $max);
|
||||||
if ($posts !== false) {
|
if ($posts !== false) {
|
||||||
return $posts;
|
return $posts;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$posts = self::getPostsFromDb($dbh, $userid, $min, $max, $limit);
|
$posts = self::getPostsFromDb($dbh, $userid, $min, $max);
|
||||||
if ($posts === false) {
|
if ($posts === false) {
|
||||||
$posts = [];
|
$posts = [];
|
||||||
}
|
}
|
||||||
|
@ -54,8 +52,9 @@ class Posts
|
||||||
foreach ($posts as $post) {
|
foreach ($posts as $post) {
|
||||||
$published = $post->getPublished();
|
$published = $post->getPublished();
|
||||||
if ($published != null) {
|
if ($published != null) {
|
||||||
if ($latestPublished === null || $published > $latestPublished) {
|
$publishedStr = gmdate('Y-m-d H:i:s', $published);
|
||||||
$latestPublished = $published;
|
if ($latestPublished === null || $publishedStr > $latestPublished) {
|
||||||
|
$latestPublished = $publishedStr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,9 +63,8 @@ class Posts
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch newer posts from connector (if any) if max is not set and limit not reached
|
// Always fetch newer posts from connector (if any)
|
||||||
if ($max == 0 && sizeof($posts) < $limit) {
|
$newPosts = $connector->getRemotePostsByUser($userid, $remoteMin, $max);
|
||||||
$newPosts = $connector->getRemotePostsByUser($userid, $remoteMin, $max, $limit);
|
|
||||||
if ($newPosts !== false && is_array($newPosts)) {
|
if ($newPosts !== false && is_array($newPosts)) {
|
||||||
// Merge new posts with DB posts, avoiding duplicates by ID
|
// Merge new posts with DB posts, avoiding duplicates by ID
|
||||||
$existingIds = [];
|
$existingIds = [];
|
||||||
|
@ -75,16 +73,10 @@ class Posts
|
||||||
}
|
}
|
||||||
foreach ($newPosts as $newPost) {
|
foreach ($newPosts as $newPost) {
|
||||||
if (!isset($existingIds[$newPost->getID()])) {
|
if (!isset($existingIds[$newPost->getID()])) {
|
||||||
if ($newPost->getID() !== "") {
|
|
||||||
self::savePost($dbh, $userid, $newPost);
|
|
||||||
}
|
|
||||||
if (sizeof($posts) < $limit) {
|
|
||||||
$posts[] = $newPost;
|
$posts[] = $newPost;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$originUrl = 'localhost';
|
$originUrl = 'localhost';
|
||||||
if (isset($_SERVER['HTTP_HOST'])) {
|
if (isset($_SERVER['HTTP_HOST'])) {
|
||||||
|
@ -103,8 +95,11 @@ class Posts
|
||||||
$originUrl = 'localhost'; // Fallback to localhost if no origin is set
|
$originUrl = 'localhost'; // Fallback to localhost if no origin is set
|
||||||
}
|
}
|
||||||
|
|
||||||
// optionally convert from article to note
|
// save posts to DB
|
||||||
foreach ($posts as $post) {
|
foreach ($posts as $post) {
|
||||||
|
if ($post->getID() !== "") {
|
||||||
|
self::savePost($dbh, $userid, $post);
|
||||||
|
}
|
||||||
switch (strtolower($post->getType())) {
|
switch (strtolower($post->getType())) {
|
||||||
case 'undo':
|
case 'undo':
|
||||||
$object = $post->getObject();
|
$object = $post->getObject();
|
||||||
|
@ -139,7 +134,7 @@ class Posts
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($cache !== null) {
|
if ($cache !== null) {
|
||||||
$cache->saveRemotePostsByUser($userid, $min, $max, $limit, $posts);
|
$cache->saveRemotePostsByUser($userid, $posts);
|
||||||
}
|
}
|
||||||
return $posts;
|
return $posts;
|
||||||
}
|
}
|
||||||
|
@ -149,27 +144,26 @@ class Posts
|
||||||
*
|
*
|
||||||
* @param \mysqli $dbh
|
* @param \mysqli $dbh
|
||||||
* @param string $userId
|
* @param string $userId
|
||||||
* @param int $min min timestamp
|
* @param string|null $min
|
||||||
* @param int $max max timestamp
|
* @param string|null $max
|
||||||
* @param int $limit
|
|
||||||
* @return \Federator\Data\ActivityPub\Common\Activity[]|false
|
* @return \Federator\Data\ActivityPub\Common\Activity[]|false
|
||||||
*/
|
*/
|
||||||
public static function getPostsFromDb($dbh, $userId, $min, $max, $limit = 20)
|
public static function getPostsFromDb($dbh, $userId, $min = null, $max = null)
|
||||||
{
|
{
|
||||||
$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`, `published` FROM posts WHERE user_id = ?';
|
||||||
$params = [$userId];
|
$params = [$userId];
|
||||||
$types = 's';
|
$types = 's';
|
||||||
if ($min > 0) {
|
if ($min !== null && $min !== "") {
|
||||||
$sql .= ' AND published >= from_unixtime(?)';
|
$sql .= ' AND published >= ?';
|
||||||
$params[] = $min;
|
$params[] = $min;
|
||||||
$types .= 's';
|
$types .= 's';
|
||||||
}
|
}
|
||||||
if ($max > 0) {
|
if ($max !== null && $max !== "") {
|
||||||
$sql .= ' AND published <= from_unixtime(?)';
|
$sql .= ' AND published <= ?';
|
||||||
$params[] = $max;
|
$params[] = $max;
|
||||||
$types .= 's';
|
$types .= 's';
|
||||||
}
|
}
|
||||||
$sql .= ' ORDER BY published DESC LIMIT ' . $limit;
|
$sql .= ' ORDER BY published DESC';
|
||||||
|
|
||||||
$stmt = $dbh->prepare($sql);
|
$stmt = $dbh->prepare($sql);
|
||||||
if ($stmt === false) {
|
if ($stmt === false) {
|
||||||
|
@ -199,7 +193,9 @@ class Posts
|
||||||
}
|
}
|
||||||
if (isset($row['published']) && $row['published'] !== null) {
|
if (isset($row['published']) && $row['published'] !== null) {
|
||||||
// If it's numeric, keep as int. If it's a string, try to parse as ISO 8601.
|
// If it's numeric, keep as int. If it's a string, try to parse as ISO 8601.
|
||||||
if (!is_numeric($row['published'])) {
|
if (is_numeric($row['published'])) {
|
||||||
|
$row['published'] = intval($row['published'], 10);
|
||||||
|
} else {
|
||||||
// Try to parse as datetime string
|
// Try to parse as datetime string
|
||||||
$timestamp = strtotime($row['published']);
|
$timestamp = strtotime($row['published']);
|
||||||
$row['published'] = $timestamp !== false ? $timestamp : null;
|
$row['published'] = $timestamp !== false ? $timestamp : null;
|
||||||
|
|
|
@ -1,193 +0,0 @@
|
||||||
<?php
|
|
||||||
/**
|
|
||||||
* SPDX-FileCopyrightText: 2024 Sascha Nitsch (grumpydeveloper) https://contentnation.net/@grumpydevelop
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
*
|
|
||||||
* @author Sascha Nitsch (grumpydeveloper)
|
|
||||||
**/
|
|
||||||
|
|
||||||
namespace Federator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* test functions
|
|
||||||
*/
|
|
||||||
class Test
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* fetch outbox
|
|
||||||
* @param string $_name user handle to fetch
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public static function fetchOutbox($_name)
|
|
||||||
{
|
|
||||||
// make webfinger request
|
|
||||||
$r = self::webfinger($_name);
|
|
||||||
$outbox = $r['outbox'];
|
|
||||||
$headers = ['Accept: application/activity+json'];
|
|
||||||
[$response, $info] = \Federator\Main::getFromRemote($outbox, $headers);
|
|
||||||
if ($info['http_code'] != 200) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$json = json_decode($response, true);
|
|
||||||
if ($json === false) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$ap = \Federator\Data\ActivityPub\Factory::newFromJson($json, $response);
|
|
||||||
if (!($ap instanceof \Federator\Data\ActivityPub\Common\OrderedCollection)) {
|
|
||||||
echo "unsupport reply from $outbox\n";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// first and total items should be set
|
|
||||||
$total = $ap->getTotalItems();
|
|
||||||
echo "total items: " . $total;
|
|
||||||
echo " first: " . $ap->getFirst() . "\n";
|
|
||||||
|
|
||||||
$page = $ap->getFirst();
|
|
||||||
$count = 0;
|
|
||||||
|
|
||||||
while ($count < $total) {
|
|
||||||
echo "query $page\n";
|
|
||||||
// query pages
|
|
||||||
[$response, $info] = \Federator\Main::getFromRemote($page, $headers);
|
|
||||||
if ($info['http_code'] != 200) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// echo "'$response'\n";
|
|
||||||
$json = json_decode($response, true);
|
|
||||||
$ap = \Federator\Data\ActivityPub\Factory::newFromJson($json, $response);
|
|
||||||
if (!($ap instanceof \Federator\Data\ActivityPub\Common\OrderedCollectionPage)) {
|
|
||||||
echo "unsupport reply from $page\n";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$thisCount = $ap->getCount();
|
|
||||||
echo "count: " . $thisCount . "\n";
|
|
||||||
for ($i = 0; $i < $thisCount; ++$i) {
|
|
||||||
$entry = $ap->get($i);
|
|
||||||
if ($entry instanceof \Federator\Data\ActivityPub\Common\APObject) {
|
|
||||||
echo $entry->getID() . " " . $entry->getPublished() . "\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$count += $thisCount;
|
|
||||||
$page = $ap->getNext();
|
|
||||||
}
|
|
||||||
//print_r($ap);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* run test
|
|
||||||
*
|
|
||||||
* @param int $argc number of arguments
|
|
||||||
* @param string[] $argv arguments
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public static function run($argc, $argv)
|
|
||||||
{
|
|
||||||
date_default_timezone_set("Europe/Berlin");
|
|
||||||
spl_autoload_register(static function (string $className) {
|
|
||||||
include PROJECT_ROOT . '/php/' . str_replace("\\", "/", strtolower($className)) . '.php';
|
|
||||||
});
|
|
||||||
if ($argc < 2) {
|
|
||||||
self::printUsage();
|
|
||||||
}
|
|
||||||
// pretend that we are running from web directory
|
|
||||||
define('PROJECT_ROOT', dirname(__DIR__, 2));
|
|
||||||
$api = new \Federator\Api();
|
|
||||||
for ($i = 1; $i < $argc; ++$i) {
|
|
||||||
switch ($argv[$i]) {
|
|
||||||
case 'fetchoutbox':
|
|
||||||
self::fetchOutbox($argv[$i + 1]);
|
|
||||||
++$i;
|
|
||||||
break;
|
|
||||||
case 'upvote':
|
|
||||||
self::upvote($api, $argv[$i + 1]);
|
|
||||||
++$i;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
self::printUsage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* print usage of maintenance tool
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public static function printUsage()
|
|
||||||
{
|
|
||||||
echo "usage php maintenance.php <command> <parameter> [<command> <parameter>...]\n";
|
|
||||||
echo "command can be one of:\n";
|
|
||||||
echo " fetchoutbox - fetch users outbox. parameter: username\n";
|
|
||||||
echo " upvote - upvote. parameter: URL to upvote\n";
|
|
||||||
echo " downvote - downvote. parameter: URL to downvote\n";
|
|
||||||
echo " comment - downvote. parameter: URL to comment, text to comment\n";
|
|
||||||
echo " Run this after you updated the program files\n";
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* upvote given URL
|
|
||||||
*
|
|
||||||
* @param \Federator\Api $api api instance
|
|
||||||
* @param string $_url URL to upvote
|
|
||||||
* @note uses hardcoded source
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public static function upvote($api, $_url)
|
|
||||||
{
|
|
||||||
$dbh = $api->getDatabase();
|
|
||||||
$inboxActivity = new \Federator\Data\ActivityPub\Common\Create();
|
|
||||||
\Federator\Api\FedUsers\Inbox::postForUser(
|
|
||||||
$dbh,
|
|
||||||
$api->getConnector(),
|
|
||||||
null,
|
|
||||||
'grumpydevelop',
|
|
||||||
$_url,
|
|
||||||
$inboxActivity
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* do a webfinger request
|
|
||||||
* @param string $_name name to query
|
|
||||||
*/
|
|
||||||
private static function webfinger($_name): mixed
|
|
||||||
{
|
|
||||||
// make webfinger request
|
|
||||||
if (preg_match("/^([^@]+)@(.*)$/", $_name, $matches) != 1) {
|
|
||||||
echo "username is malformed";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
$remoteURL = 'https://' . $matches[2] . '/.well-known/webfinger?resource=acct:' . urlencode($_name);
|
|
||||||
$headers = ['Accept: application/activity+json'];
|
|
||||||
[$response, $info] = \Federator\Main::getFromRemote($remoteURL, $headers);
|
|
||||||
if ($info['http_code'] != 200) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
$r = json_decode($response, true);
|
|
||||||
if (isset($r['links'])) {
|
|
||||||
foreach ($r['links'] as $link) {
|
|
||||||
if (isset($link['rel']) && $link['rel'] === 'self') {
|
|
||||||
$remoteURL = $link['href'];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!isset($remoteURL)) {
|
|
||||||
echo "FedUser::getUserByName Failed to find self link in webfinger for " . $_name . "\n";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// fetch the user
|
|
||||||
$headers = ['Accept: application/activity+json'];
|
|
||||||
[$response, $info] = \Federator\Main::getFromRemote($remoteURL, $headers);
|
|
||||||
if ($info['http_code'] != 200) {
|
|
||||||
echo "FedUser::getUserByName Failed to fetch user from remoteUrl for " . $_name . "\n";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
$r = json_decode($response, true);
|
|
||||||
return $r;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Test::run($argc, $argv);
|
|
|
@ -109,23 +109,21 @@ class ContentNation implements Connector
|
||||||
* get posts by given user
|
* get posts by given user
|
||||||
*
|
*
|
||||||
* @param string $userId user id
|
* @param string $userId user id
|
||||||
* @param int $min min date
|
* @param string $min min date
|
||||||
* @param int $max max date
|
* @param string $max max date
|
||||||
* @param int $limit limit results
|
|
||||||
* @unused-param $limit
|
|
||||||
* @return \Federator\Data\ActivityPub\Common\Activity[]|false
|
* @return \Federator\Data\ActivityPub\Common\Activity[]|false
|
||||||
*/
|
*/
|
||||||
public function getRemotePostsByUser($userId, $min, $max, $limit)
|
public function getRemotePostsByUser($userId, $min, $max)
|
||||||
{
|
{
|
||||||
if (preg_match("#^([^@]+)@([^/]+)#", $userId, $matches) == 1) {
|
if (preg_match("#^([^@]+)@([^/]+)#", $userId, $matches) == 1) {
|
||||||
$userId = $matches[1];
|
$userId = $matches[1];
|
||||||
}
|
}
|
||||||
$remoteURL = $this->service . '/api/profile/' . urlencode($userId) . '/activities';
|
$remoteURL = $this->service . '/api/profile/' . urlencode($userId) . '/activities';
|
||||||
if ($min > 0) {
|
if ($min !== '') {
|
||||||
$remoteURL .= '&minTS=' . intval($min, 10);
|
$remoteURL .= '&minTS=' . urlencode($min);
|
||||||
}
|
}
|
||||||
if ($max > 0) {
|
if ($max !== '') {
|
||||||
$remoteURL .= '&maxTS=' . intval($max, 10);
|
$remoteURL .= '&maxTS=' . urlencode($max);
|
||||||
}
|
}
|
||||||
[$response, $info] = \Federator\Main::getFromRemote($remoteURL, []);
|
[$response, $info] = \Federator\Main::getFromRemote($remoteURL, []);
|
||||||
if ($info['http_code'] != 200) {
|
if ($info['http_code'] != 200) {
|
||||||
|
@ -150,11 +148,12 @@ class ContentNation implements Connector
|
||||||
case 'Article':
|
case 'Article':
|
||||||
$create = new \Federator\Data\ActivityPub\Common\Create();
|
$create = new \Federator\Data\ActivityPub\Common\Create();
|
||||||
$create->setAActor($ourUrl . '/' . $userId);
|
$create->setAActor($ourUrl . '/' . $userId);
|
||||||
$create->setPublished($activity['published'] ?? $activity['timestamp'])
|
$create->setID($activity['id'])
|
||||||
|
->setPublished($activity['published'] ?? $activity['timestamp'])
|
||||||
->addTo($ourUrl . '/' . $userId . '/followers')
|
->addTo($ourUrl . '/' . $userId . '/followers')
|
||||||
->addCC("https://www.w3.org/ns/activitystreams#Public");
|
->addCC("https://www.w3.org/ns/activitystreams#Public");
|
||||||
$create->setURL($ourUrl . '/' . $activity['profilename'] . '/' . $activity['name']);
|
$create->setURL($ourUrl . '/' . $activity['profilename'] . '/' . $activity['name']);
|
||||||
$create->setID($ourUrl . '/' . $activity['profilename'] . '/' . $activity['name']);
|
$create->setID($ourUrl . '/' . $activity['profilename'] . '/' . $activity['id']);
|
||||||
$apArticle = new \Federator\Data\ActivityPub\Common\Article();
|
$apArticle = new \Federator\Data\ActivityPub\Common\Article();
|
||||||
if (array_key_exists('tags', $activity)) {
|
if (array_key_exists('tags', $activity)) {
|
||||||
foreach ($activity['tags'] as $tag) {
|
foreach ($activity['tags'] as $tag) {
|
||||||
|
|
|
@ -46,12 +46,11 @@ class DummyConnector implements Connector
|
||||||
* get posts by given user
|
* get posts by given user
|
||||||
*
|
*
|
||||||
* @param string $id user id @unused-param
|
* @param string $id user id @unused-param
|
||||||
* @param int $min min timestamp @unused-param
|
* @param string $min min date @unused-param
|
||||||
* @param int $max max timestamp @unused-param
|
* @param string $max max date @unused-param
|
||||||
* @param int $limit limit number of results @unused-param
|
|
||||||
* @return \Federator\Data\ActivityPub\Common\Activity[]|false
|
* @return \Federator\Data\ActivityPub\Common\Activity[]|false
|
||||||
*/
|
*/
|
||||||
public function getRemotePostsByUser($id, $min, $max, $limit)
|
public function getRemotePostsByUser($id, $min, $max)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -139,13 +139,12 @@ class RedisCache implements Cache
|
||||||
* get posts by given user
|
* get posts by given user
|
||||||
*
|
*
|
||||||
* @param string $id user id @unused-param
|
* @param string $id user id @unused-param
|
||||||
* @param int $min min timestamp @unused-param
|
* @param string $min min date @unused-param
|
||||||
* @param int $max max timestamp @unused-param
|
* @param string $max max date @unused-param
|
||||||
* @param int $limit limit results @unused-param
|
|
||||||
|
|
||||||
* @return \Federator\Data\ActivityPub\Common\Activity[]|false
|
* @return \Federator\Data\ActivityPub\Common\Activity[]|false
|
||||||
*/
|
*/
|
||||||
public function getRemotePostsByUser($id, $min, $max, $limit)
|
public function getRemotePostsByUser($id, $min, $max)
|
||||||
{
|
{
|
||||||
error_log("rediscache::getRemotePostsByUser not implemented");
|
error_log("rediscache::getRemotePostsByUser not implemented");
|
||||||
return false;
|
return false;
|
||||||
|
@ -274,13 +273,10 @@ class RedisCache implements Cache
|
||||||
* save remote posts by user
|
* save remote posts by user
|
||||||
*
|
*
|
||||||
* @param string $user user name @unused-param
|
* @param string $user user name @unused-param
|
||||||
* @param int $min min timestamp @unused-param
|
|
||||||
* @param int $max max timestamp @unused-param
|
|
||||||
* @param int $limit limit results @unused-param
|
|
||||||
* @param \Federator\Data\ActivityPub\Common\APObject[]|false $posts user posts @unused-param
|
* @param \Federator\Data\ActivityPub\Common\APObject[]|false $posts user posts @unused-param
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function saveRemotePostsByUser($user, $min, $max, $limit, $posts)
|
public function saveRemotePostsByUser($user, $posts)
|
||||||
{
|
{
|
||||||
error_log("rediscache::saveRemotePostsByUser not implemented");
|
error_log("rediscache::saveRemotePostsByUser not implemented");
|
||||||
}
|
}
|
||||||
|
@ -309,9 +305,6 @@ class RedisCache implements Cache
|
||||||
*/
|
*/
|
||||||
public function saveRemoteUserByName($_name, $user)
|
public function saveRemoteUserByName($_name, $user)
|
||||||
{
|
{
|
||||||
if (!$this->connected) {
|
|
||||||
$this->connect();
|
|
||||||
}
|
|
||||||
$key = self::createKey('u', $_name);
|
$key = self::createKey('u', $_name);
|
||||||
$serialized = $user->toJson();
|
$serialized = $user->toJson();
|
||||||
$this->redis->setEx($key, $this->userTTL, $serialized);
|
$this->redis->setEx($key, $this->userTTL, $serialized);
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
{ldelim}
|
{ldelim}
|
||||||
"subject": "acct:{$username}@{$domain}",
|
"subject": "acct:{$username}@{$domain}",
|
||||||
"aliases": [
|
"aliases": [
|
||||||
"https://{$domain}/@{$username}"
|
"https://{$domain}/@{$username}",
|
||||||
|
"https://{$domain}/users/{$username}"
|
||||||
],
|
],
|
||||||
"links": [
|
"links": [
|
||||||
{ldelim}"rel": "self", "type": "application/activity+json", "href": "https://{$domain}/{$username}"{rdelim},
|
{ldelim}"rel": "self", "type": "application/activity+json", "href": "https://{$domain}/{$username}"{rdelim},
|
||||||
|
|
Loading…
Add table
Reference in a new issue