hostas2/public/api/v1/actor.php

212 lines
6.4 KiB
PHP

<?php
require_once($_SERVER['DOCUMENT_ROOT'] . '/config.php');
require_once($_SERVER['DOCUMENT_ROOT'] . '/api/v1/database.php');
function get_actor_or_exit($conn, string $preferred_username) {
$actor = sql_fetch_actor($conn, $preferred_username);
if (!$actor) {
http_response_code(404);
die();
}
return $actor;
}
function get_actor(string $preferred_username) {
$public_key = file_get_contents(HOSTAS_PUBKEY_PATH);
$conn = new SQLite3(HOSTAS_DATABASE_PATH);
$actor = get_actor_or_exit($conn, $preferred_username);
$activity_representation = array(
'@context' => HOSTAS_CONTEXT,
'id' => $actor['id'],
'type' => 'Person',
'preferredUsername' => $actor['preferredUsername'],
'name' => $actor['name'],
'summary' => $actor['summary'],
'url' => $actor['url'],
'icon' => array(
'type' => 'Image',
'url' => $actor['icon'],
),
'publicKey' => array(
'id' => "{$actor['id']}#main-key",
'owner' => $actor['id'],
'publicKeyPem' => $public_key,
),
'inbox' => "https://" . HOSTAS_DOMAIN . "/api/v1/actor/$preferred_username/inbox",
'outbox' => "https://" . HOSTAS_DOMAIN . "/api/v1/actor/$preferred_username/outbox",
);
header('Content-Type: application/activity+json');
echo json_encode($activity_representation);
$conn->close();
die();
}
/** /api/v1/actor/:preferred_username/outbox?before=:before_date_iso&limit=:limit
* Returns an OrderedCollectionPage of at most `limit` pages published before
* `before_date_iso`.
*
* Parameters:
* before ISO timestamp string. Only posts published before this time are included
* limit Integer. Number of posts to include in the result. Default: 20. Max: HOSTAS_MAX_POSTS_PER_PAGE
*/
function get_actor_outbox_paged(string $preferred_username) {
header('Content-Type: application/activity+json');
$before = array_key_exists('before', $_GET) ? $_GET['before'] : date(DATE_ISO8601, time());
$after = array_key_exists('after', $_GET) ? $_GET['after'] : date(DATE_ISO8601, 0);
$limit = array_key_exists('limit', $_GET) ? $_GET['limit'] : 20;
// Parameters in $_GET with a + character will get replaced with whitespace,
// so we've got to change it back
$before = str_replace(" ", "+", $before);
$after = str_replace(" ", "+", $after);
// Cap the limit at the configured maximum
if ($limit > HOSTAS_MAX_POSTS_PER_PAGE) {
$limit = HOSTAS_MAX_POSTS_PER_PAGE;
}
$conn = new SQLite3(HOSTAS_DATABASE_PATH);
$actor = get_actor_or_exit($conn, $preferred_username);
$sql_query = <<<'EOT'
select object.id as object_id, object.type as object_type,
activity.actor as activity_actor, post.id as post_id,
post.published as post_published, post.type as post_type,
post.content as post_content, post.published as post_published
from object
join activity on activity.objectId = object.id
join object as post on activity.object = post.id
where
activity_actor = :actor_id
and post_published < :before
and post_published > :after
order by object.published desc
limit :limit
EOT;
$create_activities_result = prepare_and_execute($conn, $sql_query,
array(
':actor_id' => $actor['id'],
':before' => strtotime($before),
':after' => strtotime($after),
':limit' => $limit
)
);
$total_items = 0;
$ordered_items = array();
while ($entry = $create_activities_result->fetchArray()) {
$total_items += 1;
array_push($ordered_items, array(
'id' => $entry['object_id'],
'type' => $entry['object_type'],
'actor' => $entry['activity_actor'],
'published' => date(DATE_ISO8601, $entry['post_published']),
'cc' => array('https://www.w3.org/ns/activitystreams#Public'),
'object' => array(
'id' => $entry['post_id'],
'type'=> $entry['post_type'],
'published' => date(DATE_ISO8601, $entry['post_published']),
'url' => $entry['post_id'],
'attributedTo' => $actor['id'],
'cc' => array('https://www.w3.org/ns/activitystreams#Public'),
'content' => $entry['post_content'],
),
));
}
$clean_uri = 'https://' . HOSTAS_DOMAIN . strtok($_SERVER['REQUEST_URI'], '?');
$response = array(
'@context' => HOSTAS_CONTEXT,
'id' => 'https://' . HOSTAS_DOMAIN . $_SERVER['REQUEST_URI'],
'type' => 'OrderedCollectionPage',
'partOf' => 'https://' . HOSTAS_DOMAIN . $clean_uri,
'orderedItems' => $ordered_items,
);
if ($total_items === 0) {
// There may be previous items, so we can reuse the before param. We
// need to do some pre-processing to avoid characters getting escaped
// from the URL
$latest_time = str_replace(" ", "+", $before);
$response['prev'] = $clean_uri . "?after=$latest_time";
} else {
$earliest_time = $ordered_items[array_key_last($ordered_items)]['published'];
$latest_time = $ordered_items[0]['published'];
$response['prev'] = $clean_uri . "?after=$latest_time";
$response['next'] = $clean_uri . "?before=$earliest_time";
}
echo json_encode($response);
die();
}
function get_actor_outbox(string $preferred_username) {
header('Content-Type: application/activity+json');
$conn = new SQLite3(HOSTAS_DATABASE_PATH);
$actor = get_actor_or_exit($conn, $preferred_username);
$sql_query = <<<'EOT'
select count(post.id) as post_count, min(post.published)
from object
join activity on activity.objectId = object.id
join object as post on activity.object = post.id
where activity.actor = :actor_id
EOT;
$items_count_result = prepare_and_execute($conn, $sql_query,
array(':actor_id' => $actor['id'])
);
$items_count_row = $items_count_result->fetchArray(SQLITE3_ASSOC);
$total_items = $items_count_row['post_count'];
$collection_id = "https://" . HOSTAS_DOMAIN . "/api/v1/actor/$preferred_username/outbox";
echo json_encode(array(
'@context' => HOSTAS_CONTEXT,
'id' => $collection_id,
'type' => 'OrderedCollection',
'totalItems' => $total_items,
'first' => $collection_id . '?before=' . date(DATE_ISO8601, time()),
'last' => $collection_id . '?after=' . date(DATE_ISO8601, 0),
));
die();
}
function get_actor_inbox(string $objectId) {
die();
}
switch ($_SERVER['REQUEST_METHOD']) {
case 'GET':
if (sizeof(REQUEST_PATH) === 3) {
if (REQUEST_PATH[2] === 'inbox') get_actor_inbox(REQUEST_PATH[1]);
else if (REQUEST_PATH[2] === 'outbox') {
if (array_key_exists('before', $_GET) || array_key_exists('after', $_GET)) {
get_actor_outbox_paged(REQUEST_PATH[1]);
} else {
get_actor_outbox(REQUEST_PATH[1]);
}
}
}
get_actor(REQUEST_PATH[1]);
break;
default:
http_response_code(405);
die();
}