[activitypub] v0.0.2: make posts loadable by url, with text and attached imgs

This commit is contained in:
octospacc 2023-09-13 02:02:54 +02:00
parent 2067f380b5
commit 5bd77be244
7 changed files with 244 additions and 64 deletions

View File

@ -14,14 +14,16 @@
<fieldset> <fieldset>
<legend>{{ lang('SETTINGS') }}</legend> <legend>{{ lang('SETTINGS') }}</legend>
<dl> <dl>
<dt><label for="spaccincphpbb_activitypub_setfederation">{{ lang('ACP_SPACCINC_ACTIVITYPUB_SETFEDERATION') ~ lang('COLON') }}</label></dt> <dt><label for="spaccinc_activitypub_setfederation">{{ lang('ACP_SPACCINC_ACTIVITYPUB_SETFEDERATION') ~ lang('COLON') }}</label></dt>
<dd><label><input type="radio" class="radio" name="spaccincphpbb_activitypub_setfederation" value="1"{% if SPACCINCPHPBB_ACTIVITYPUB_SETFEDERATION %} checked="checked"{% endif %} /> {{ lang('YES') }}</label> <dd><label><input type="radio" class="radio" name="spaccinc_activitypub_setfederation" value="1" {% if SPACCINC_ACTIVITYPUB_SETFEDERATION %} checked="checked" {% endif %} /> {{ lang('YES') }}</label>
<label><input type="radio" class="radio" name="spaccincphpbb_activitypub_setfederation" value="0"{% if not SPACCINCPHPBB_ACTIVITYPUB_SETFEDERATION %} checked="checked"{% endif %} /> {{ lang('NO') }}</label></dd> <label><input type="radio" class="radio" name="spaccinc_activitypub_setfederation" value="0" {% if not SPACCINC_ACTIVITYPUB_SETFEDERATION %} checked="checked" {% endif %} /> {{ lang('NO') }}</label></dd>
</dl> </dl>
<!--<dl> <!--
<dt><label for="spaccincphpbb_activitypub_setdomain">{{ lang('ACP_SPACCINC_ACTIVITYPUB_SETDOMAIN') ~ lang('COLON') }}</label><br /><span>{{ lang('ACP_SPACCINC_ACTIVITYPUB_SETDOMAIN_INFO') }}</span></dt> <dl>
<dd><input name="spaccincphpbb_activitypub_setdomain" type="text" placeholder="TODO" value="TODO" disabled="TODO" /></dd> <dt><label for="spaccinc_activitypub_setdomain">{{ lang('ACP_SPACCINC_ACTIVITYPUB_SETDOMAIN') ~ lang('COLON') }}</label><br /><span>{{ lang('ACP_SPACCINC_ACTIVITYPUB_SETDOMAIN_INFO') }}</span></dt>
</dl>--> <dd><input name="spaccinc_activitypub_setdomain" type="text" placeholder="{{ SYS_SERVER_NAME }}" value="{{ SPACCINC_ACTIVITYPUB_SETDOMAIN }}" {% if SPACCINC_ACTIVITYPUB_SETDOMAIN != '' %} disabled="disabled" {% endif %} /></dd>
</dl>
-->
</fieldset> </fieldset>
<fieldset class="submit-buttons"> <fieldset class="submit-buttons">

View File

@ -3,8 +3,8 @@
"type": "phpbb-extension", "type": "phpbb-extension",
"description": "ActivityPub for phpBB", "description": "ActivityPub for phpBB",
"homepage": "https://gitlab.com/SpaccInc/SpaccCommunityPlatform", "homepage": "https://gitlab.com/SpaccInc/SpaccCommunityPlatform",
"version": "0.0.2-dev", "version": "0.0.2",
"time": "2023-09-09", "time": "2023-09-13",
"license": "[to be defined]", "license": "[to be defined]",
"authors": [ "authors": [
{ {

View File

@ -3,8 +3,8 @@ services:
class: spaccincphpbb\activitypub\controller\activitypub_controller class: spaccincphpbb\activitypub\controller\activitypub_controller
arguments: arguments:
- '@config' - '@config'
- '@request'
- '@dbal.conn' - '@dbal.conn'
- '@request'
spaccincphpbb.activitypub.controller.acp: spaccincphpbb.activitypub.controller.acp:
class: spaccincphpbb\activitypub\controller\acp_controller class: spaccincphpbb\activitypub\controller\acp_controller

View File

@ -43,7 +43,8 @@ class acp_controller
if (empty($errors)) if (empty($errors))
{ {
$this->config->set('spaccincphpbb_activitypub_setfederation', $this->request->variable('spaccincphpbb_activitypub_setfederation', 0)); $this->config->set('spaccinc_activitypub_setfederation', $this->request->variable('spaccinc_activitypub_setfederation', 0));
//$this->config->set('spaccinc_activitypub_setfederation', $this->request->variable('spaccinc_activitypub_setdomain', ''));
$this->log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_ACP_SPACCINC_ACTIVITYPUB_SETTINGS'); $this->log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_ACP_SPACCINC_ACTIVITYPUB_SETTINGS');
trigger_error($this->language->lang('CONFIG_UPDATED') . adm_back_link($this->u_action)); trigger_error($this->language->lang('CONFIG_UPDATED') . adm_back_link($this->u_action));
} }
@ -53,9 +54,11 @@ class acp_controller
$this->template->assign_vars([ $this->template->assign_vars([
'S_ERROR' => $s_errors, 'S_ERROR' => $s_errors,
'ERROR_MSG' => $s_errors ? implode('<br />', $errors) : '', 'ERROR_MSG' => ($s_errors ? implode('<br />', $errors) : ''),
'U_ACTION' => $this->u_action, 'U_ACTION' => $this->u_action,
'SPACCINCPHPBB_ACTIVITYPUB_SETFEDERATION' => (bool)$this->config['spaccincphpbb_activitypub_setfederation'], 'SYS_SERVER_NAME' => $this->config['server_name'],
'SPACCINC_ACTIVITYPUB_SETFEDERATION' => (bool)$this->config['spaccinc_activitypub_setfederation'],
'SPACCINC_ACTIVITYPUB_SETDOMAIN' => $this->config['spaccinc_activitypub_setdomain'],
]); ]);
} }

View File

@ -3,48 +3,45 @@
namespace spaccincphpbb\activitypub\controller; namespace spaccincphpbb\activitypub\controller;
use ErrorException; use ErrorException;
use DOMDocument;
use DOMXpath;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
class activitypub_controller class activitypub_controller
{ {
protected $config; protected $config;
protected $request;
protected $db; protected $db;
protected $request;
// Quick way to force any PHP warning to be an error that halts execution and makes nothing return, // Quick way to force any PHP warning to be an error that halts execution and makes nothing return,
// so that we don't need complex error handling in case of bad queries // so that we don't need complex error handling in case of bad queries
public function exception_error_handler($severity, $message, $file, $line) public function exception_error_handler($severity, $message, $file, $line)
{ {
if ($message !== 'Only variables should be passed by reference') if ($message !== 'Return type of phpbb\datetime::format($format = \'\', $force_absolute = false) should either be compatible with DateTime::format(string $format): string, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice' && $message !== 'Only variables should be passed by reference' && !(str_contains($message, ' service is private, getting it from the container is deprecated ') || str_contains($message, 'You should either make the service public, or stop using the container directly and use dependency injection instead.'))
{ ){
print $message;
throw new ErrorException($message, 0, $severity, $file, $line); throw new ErrorException($message, 0, $severity, $file, $line);
} }
} }
public function __construct( public function __construct(
\phpbb\config\config $config, \phpbb\config\config $config,
\phpbb\request\request $request,
\phpbb\db\driver\driver_interface $db, \phpbb\db\driver\driver_interface $db,
\phpbb\request\request $request,
){ ){
$this->config = $config; $this->config = $config;
$this->request = $request;
$this->db = $db; $this->db = $db;
$this->request = $request;
$this->site_name = $this->get_sql_row(' $this->server_name = $this->config['server_name'];
SELECT config_value
FROM ' . CONFIG_TABLE . "
WHERE config_name = 'sitename'
")['config_value'];
$this->server_name = $this->get_sql_row('
SELECT config_value
FROM ' . CONFIG_TABLE . "
WHERE config_name = 'server_name'
")['config_value'];
$this->server_addr = ((!empty($this->request->server('HTTPS')) && (strtolower($this->request->server('HTTPS')) == 'on' || $this->request->server('HTTPS') == '1')) ? 'https://' : 'http://') . $this->server_name; $this->server_addr = ((!empty($this->request->server('HTTPS')) && (strtolower($this->request->server('HTTPS')) == 'on' || $this->request->server('HTTPS') == '1')) ? 'https://' : 'http://') . $this->server_name;
} }
private function iso_time($time)
{
return gmdate("Y-m-d\TH:i:s\Z", $time);
}
private function get_sql_row($sql) private function get_sql_row($sql)
{ {
$result = $this->db->sql_query($sql); $result = $this->db->sql_query($sql);
@ -53,9 +50,122 @@ class activitypub_controller
return $data; return $data;
} }
private function get_sql_rows($sql)
{
$result = $this->db->sql_query($sql);
$data = $this->db->sql_fetchrowset($result);
$this->db->sql_freeresult($result);
return $data;
}
private function get_bbcode_flags($data)
{
return
($data['enable_bbcode'] ? OPTION_FLAG_BBCODE : 0) +
($data['enable_smilies'] ? OPTION_FLAG_SMILIES : 0) +
($data['enable_magic_url'] ? OPTION_FLAG_LINKS : 0);
}
private function make_post_attachments($html)
{
$attachments = [];
$dom = new DOMDocument;
$dom->loadHTML($html);
$xpath = new DOMXpath($dom);
$imgs = $dom->getElementsByTagName('img');
$atts = $xpath->query('//div[@class="inline-attachment"]');
// TODO: currently this picks up emojis, and must so be fixed
//foreach($imgs as $item){
// $attachments[] = [
// 'type' => 'Document',
// 'mediaType' => 'image/*',
// 'url' => $item->getAttribute('src'),
// //'name' => null,
// ];
//}
unset($item);
foreach($atts as $item){
$file_name = explode('<!--', explode('-->', $item->ownerDocument->saveHtml($item))[1])[0];
$file_id = $this->get_sql_row('
SELECT attach_id
FROM ' . ATTACHMENTS_TABLE . "
WHERE real_filename='" . $this->db->sql_escape($file_name) . "'
")['attach_id'];
$attachments[] = [
'type' => 'Document',
'mediaType' => 'image/jpeg',
'url' => $this->server_addr . '/download/file.php?id=' . $file_id,
'name' => null,
];
}
unset($item);
return $attachments;
}
private function make_post_object($data, $in_create)
{
$uri_id = $this->server_addr . '/activitypub?&mode=post&post_id=' . $data['post_id'];
$uri_user = $this->server_addr . '/activitypub?&mode=user&user_id=' . $data['poster_id'];
$post_html = generate_text_for_display($data['post_text'], $data['bbcode_uid'], $data['bbcode_bitfield'], $this->get_bbcode_flags($data));
$post_time = $this->iso_time($data['post_time']);
// Note: #Public in to and followers in cc for public post, opposite for unlisted!
$post_to = ['https://www.w3.org/ns/activitystreams#Public'];
$post_cc = [];
$note = [
'id' => $uri_id,
'type' => 'Note',
'published' => $post_time,
//'updated' => ,
'attributedTo' => $uri_user,
//'inReplyTo' => null,
'to' => $post_to,
'cc' => $post_cc,
'url' => $this->server_addr . '/viewtopic.php?p=' . $data['post_id'] . '#p' . $data['post_id'],
//'mediaType' => 'text/html',
//'summary' => null,
'content' => $post_html,
//'contentMap' => [ 'it' => '' ],
'attachment' => $this->make_post_attachments($post_html),
];
if ($in_create)
{
return [
'@context' => [
'https://www.w3.org/ns/activitystreams',
],
'id' => $uri_id,
'type' => 'Create',
'actor' => $uri_user,
'published' => $post_time,
//'updated' => ,
'to' => $post_to,
'cc' => $post_cc,
'object' => $note,
];
}
else
{
$response[] = $note;
return array_merge([
'@context' => [
'https://www.w3.org/ns/activitystreams',
]
], $note);
}
}
public function nodeinfo_known() public function nodeinfo_known()
{ {
if (!$this->config['spaccincphpbb_activitypub_setfederation']) { if (!$this->config['spaccinc_activitypub_setfederation']) {
return; return;
} }
@ -74,7 +184,7 @@ class activitypub_controller
public function webfinger() public function webfinger()
{ {
if (!$this->config['spaccincphpbb_activitypub_setfederation']) { if (!$this->config['spaccinc_activitypub_setfederation']) {
return; return;
} }
@ -86,11 +196,6 @@ class activitypub_controller
$subject = ''; $subject = '';
$href = []; $href = [];
//if ($resource == '')
//{
// return new JsonResponse([], 500);
//}
if (str_starts_with($resource, 'acct:')) if (str_starts_with($resource, 'acct:'))
{ {
$tokens = explode('@', substr($resource, strlen('acct:'))); $tokens = explode('@', substr($resource, strlen('acct:')));
@ -107,8 +212,8 @@ class activitypub_controller
WHERE username_clean = '" . $this->db->sql_escape($name) . "' WHERE username_clean = '" . $this->db->sql_escape($name) . "'
"); ");
$href['self'] = '&mode=user&u=' . $data['user_id']; $href['self'] = '&mode=user&user_id=' . $data['user_id'];
$href['profile-page'] = '/memberlist.php?mode=viewprofile&u=' . $data['user_id']; $href['profile-page'] = '/memberlist.php?mode=viewprofile&user_id=' . $data['user_id'];
} }
$response = new Response(json_encode([ $response = new Response(json_encode([
@ -130,7 +235,7 @@ class activitypub_controller
public function activitypub() public function activitypub()
{ {
if (!$this->config['spaccincphpbb_activitypub_setfederation']) { if (!$this->config['spaccinc_activitypub_setfederation']) {
return; return;
} }
@ -139,7 +244,7 @@ class activitypub_controller
$uri_id = htmlspecialchars_decode($server_addr . $this->request->server('REQUEST_URI')); $uri_id = htmlspecialchars_decode($server_addr . $this->request->server('REQUEST_URI'));
$mode = $this->request->variable('mode', ''); $mode = $this->request->variable('mode', '');
$response = []; $response = null;
switch ($mode) switch ($mode)
{ {
@ -173,7 +278,7 @@ class activitypub_controller
//], //],
//'openRegistrations' => true, //'openRegistrations' => true,
'metadata' => [ 'metadata' => [
'nodeName' => $this->site_name, 'nodeName' => $this->config['sitename'],
], ],
]; ];
break; break;
@ -181,12 +286,12 @@ class activitypub_controller
break; break;
case 'user': case 'user':
$u = $this->request->variable('u', ''); $user_id = $this->request->variable('user_id', '');
$data = $this->get_sql_row(' $data = $this->get_sql_row('
SELECT * SELECT *
FROM ' . USERS_TABLE . " FROM ' . USERS_TABLE . "
WHERE user_id = '" . $this->db->sql_escape($u) . "' WHERE user_id = '" . $this->db->sql_escape($user_id) . "'
"); ");
$icon_ext = end(explode('.', $data['user_avatar'])); $icon_ext = end(explode('.', $data['user_avatar']));
@ -208,18 +313,18 @@ class activitypub_controller
'https://w3id.org/security/v1', 'https://w3id.org/security/v1',
], ],
'id' => $uri_id, 'id' => $uri_id,
'inbox' => $server_addr . '/activitypub?&mode=inbox&u=' . $u, 'inbox' => $server_addr . '/activitypub?&mode=inbox&user_id=' . $user_id,
'outbox' => $server_addr . '/activitypub?&mode=outbox&u=' . $u, 'outbox' => $server_addr . '/activitypub?&mode=outbox&user_id=' . $user_id,
'endpoints' => [ 'endpoints' => [
'sharedInbox' => $server_addr . '/activitypub&mode=inbox', 'sharedInbox' => $server_addr . '/activitypub&mode=inbox',
], ],
'type' => 'Person', 'type' => 'Person',
//'discoverable' => true, //'discoverable' => true,
//'manuallyApprovesFollowers' => false, //'manuallyApprovesFollowers' => false,
//'published' => $data['user_regdate'], 'published' => $this->iso_time($data['user_regdate']),
'preferredUsername' => $data['username_clean'], 'preferredUsername' => $data['username_clean'],
'name' => $data['username'], 'name' => $data['username'],
'url' => $server_addr . '/memberlist.php?mode=viewprofile&u=' . $u, 'url' => $server_addr . '/memberlist.php?mode=viewprofile&user_id=' . $user_id,
'icon' => [ 'icon' => [
'type' => 'Image', 'type' => 'Image',
'mediaType' => 'image/' . ($icon_ext === 'jpg' ? 'jpeg' : $icon_ext), 'mediaType' => 'image/' . ($icon_ext === 'jpg' ? 'jpeg' : $icon_ext),
@ -238,27 +343,95 @@ class activitypub_controller
break; break;
case 'outbox': case 'outbox':
$u = $this->request->variable('u', ''); $user_id = $this->request->variable('user_id', '');
$page = $this->request->variable('page', '');
//$min_id = $this->request->variable('min_id', -1);
//$max_id = $this->request->variable('max_id', -1);
$user_posts = $this->get_sql_row(' $post_count = (int)$this->get_sql_row('
SELECT user_posts SELECT user_posts
FROM ' . USERS_TABLE . " FROM ' . USERS_TABLE . "
WHERE user_id = '" . $this->db->sql_escape($u) . "' WHERE user_id = '" . $this->db->sql_escape($user_id) . "'
")['user_posts']; ")['user_posts'];
if ($page === '')
{
$response = [ $response = [
'@context' => 'https://www.w3.org/ns/activitystreams', '@context' => [
'https://www.w3.org/ns/activitystreams',
],
'id' => $uri_id, 'id' => $uri_id,
'type' => 'OrderedCollection', 'type' => 'OrderedCollection',
'totalItems' => (int)$user_posts, 'totalItems' => $post_count,
'first' => $uri_id . '&view=page', 'first' => $uri_id . '&page=-1',
'last' => $uri_id . '&view=page&min_id=0', 'last' => $uri_id . '&page=0',
]; ];
}
else
{
$items = [];
$order = 'DESC';
$limit = 20;
$offset = 0;
switch ($page)
{
// Oldest
case '0':
$order = 'DESC';
break; break;
//case 'post': // Newest
case '-1':
// ...
break;
// Any other page
default:
// ...
break;
}
$data = $this->get_sql_rows('
SELECT *
FROM ' . POSTS_TABLE . "
WHERE poster_id = '" . $this->db->sql_escape($user_id) . "'
ORDER BY post_id " . $order . '
LIMIT ' . $limit . '
OFFSET ' . $offset
);
foreach ($data as &$item)
{
$items[] = $this->make_post_object($item, true);
}
unset($item);
$response = [
'@context' => [
'https://www.w3.org/ns/activitystreams',
],
'id' => $uri_id,
'type' => 'OrderedCollectionPage',
//'prev' => '',
//'next' => '',
// ...
'partOf' => $server_addr . '/activitypub?&mode=outbox&user_id=' . $user_id,
//'ordererdItems' => $items,
];
}
break;
//case 'thread':
// //
//break;
case 'post':
$response = $this->make_post_object($this->get_sql_row('
SELECT *
FROM ' . POSTS_TABLE . "
WHERE post_id = '" . $this->db->sql_escape($this->request->variable('post_id', '')) . "'
"), false);
break;
} }
$response = new Response(json_encode($response), 200); $response = new Response(json_encode($response), 200);

View File

@ -14,5 +14,6 @@ $lang = array_merge($lang, [
'ACP_SPACCINC_ACTIVITYPUB_TITLE' => 'ActivityPub for phpBB Module', 'ACP_SPACCINC_ACTIVITYPUB_TITLE' => 'ActivityPub for phpBB Module',
'ACP_SPACCINC_ACTIVITYPUB_SETFEDERATION' => 'Enable ActivityPub federation for this board', 'ACP_SPACCINC_ACTIVITYPUB_SETFEDERATION' => 'Enable ActivityPub federation for this board',
'ACP_SPACCINC_ACTIVITYPUB_SETDOMAIN' => 'Full domain name to use for federation', 'ACP_SPACCINC_ACTIVITYPUB_SETDOMAIN' => 'Full domain name to use for federation',
'ACP_SPACCINC_ACTIVITYPUB_SETDOMAIN_INFO' => 'The domain on which federation will be handled. NEVER change this after enabling federation, unless you know the implications.', 'ACP_SPACCINC_ACTIVITYPUB_SETDOMAIN_INFO' => 'The domain on which federation will be handled. You will never be able to change this later! (unless you know all implications)',
'ACP_SPACCINC_ACTIVITYPUB_SETDOMAIN_CONFIRM' => 'I understand that the federation domain will not be able to be changed after having set it.'
]); ]);

View File

@ -6,7 +6,7 @@ class install_acp_module extends \phpbb\db\migration\migration
{ {
public function effectively_installed() public function effectively_installed()
{ {
return isset($this->config['spaccincphpbb_activitypub_setfederation']); return isset($this->config['spaccinc_activitypub_setfederation']);
} }
public static function depends_on() public static function depends_on()
@ -17,7 +17,8 @@ class install_acp_module extends \phpbb\db\migration\migration
public function update_data() public function update_data()
{ {
return [ return [
['config.add', ['spaccincphpbb_activitypub_setfederation', 0]], ['config.add', ['spaccinc_activitypub_setfederation', 0]],
['config.add', ['spaccinc_activitypub_setdomain', '']],
['module.add', [ ['module.add', [
'acp', 'acp',