Merge remote-tracking branch 'upstream/2023.03-rc' into api-edit

This commit is contained in:
Michael 2023-03-14 21:46:24 +00:00
commit e3047794f9
13 changed files with 941 additions and 1008 deletions

View file

@ -1 +1 @@
2023.03-dev
2023.03-rc

View file

@ -1,5 +1,5 @@
-- ------------------------------------------
-- Friendica 2023.03-dev (Giant Rhubarb)
-- Friendica 2023.03-rc (Giant Rhubarb)
-- DB_UPDATE_VERSION 1517
-- ------------------------------------------

View file

@ -64,7 +64,7 @@ class App
{
const PLATFORM = 'Friendica';
const CODENAME = 'Giant Rhubarb';
const VERSION = '2023.03-dev';
const VERSION = '2023.03-rc';
// Allow themes to control internal parameters
// by changing App values in theme.php

View file

@ -209,183 +209,6 @@ class BBCode
);
}
public static function getAttachedData(string $body, array $item = []): array
{
/*
- text:
- type: link, video, photo
- title:
- url:
- image:
- description:
- (thumbnail)
*/
DI::profiler()->startRecording('rendering');
$has_title = !empty($item['title']);
$plink = $item['plink'] ?? '';
$post = self::getAttachmentData($body);
// Get all linked images with alternative image description
if (preg_match_all("/\[img=(http[^\[\]]*)\]([^\[\]]*)\[\/img\]/Usi", $body, $pictures, PREG_SET_ORDER)) {
foreach ($pictures as $picture) {
if ($id = Photo::getIdForName($picture[1])) {
$post['images'][] = ['url' => str_replace('-1.', '-0.', $picture[1]), 'description' => $picture[2], 'id' => $id];
} else {
$post['remote_images'][] = ['url' => $picture[1], 'description' => $picture[2]];
}
}
if (!empty($post['images']) && !empty($post['images'][0]['description'])) {
$post['image_description'] = $post['images'][0]['description'];
}
}
if (preg_match_all("/\[img\]([^\[\]]*)\[\/img\]/Usi", $body, $pictures, PREG_SET_ORDER)) {
foreach ($pictures as $picture) {
if ($id = Photo::getIdForName($picture[1])) {
$post['images'][] = ['url' => str_replace('-1.', '-0.', $picture[1]), 'description' => '', 'id' => $id];
} else {
$post['remote_images'][] = ['url' => $picture[1], 'description' => ''];
}
}
}
if (!isset($post['type'])) {
$post['text'] = $body;
}
// Simplify image codes
$post['text'] = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '[img]$3[/img]', $post['text']);
$post['text'] = preg_replace("/\[img\=(.*?)\](.*?)\[\/img\]/ism", '[img]$1[/img]', $post['text']);
// if nothing is found, it maybe having an image.
if (!isset($post['type'])) {
if (preg_match_all("#\[url=([^\]]+?)\]\s*\[img\]([^\[]+?)\[/img\]\s*\[/url\]#ism", $post['text'], $pictures, PREG_SET_ORDER)) {
if ((count($pictures) == 1) && !$has_title) {
if (!empty($item['object-type']) && ($item['object-type'] == Activity\ObjectType::IMAGE)) {
// Replace the preview picture with the real picture
$url = str_replace('-1.', '-0.', $pictures[0][2]);
$data = ['url' => $url, 'type' => 'photo'];
} else {
// Checking, if the link goes to a picture
$data = ParseUrl::getSiteinfoCached($pictures[0][1]);
}
// Workaround:
// Sometimes photo posts to the own album are not detected at the start.
// So we seem to cannot use the cache for these cases. That's strange.
if (($data['type'] != 'photo') && strstr($pictures[0][1], '/photos/')) {
$data = ParseUrl::getSiteinfo($pictures[0][1]);
}
if ($data['type'] == 'photo') {
$post['type'] = 'photo';
if (isset($data['images'][0])) {
$post['image'] = $data['images'][0]['src'];
$post['url'] = $data['url'];
} else {
$post['image'] = $data['url'];
}
$post['preview'] = $pictures[0][2];
$post['text'] = trim(str_replace($pictures[0][0], '', $post['text']));
} else {
$imgdata = Images::getInfoFromURLCached($pictures[0][1]);
if (($imgdata) && substr($imgdata['mime'], 0, 6) == 'image/') {
$post['type'] = 'photo';
$post['image'] = $pictures[0][1];
$post['preview'] = $pictures[0][2];
$post['text'] = trim(str_replace($pictures[0][0], '', $post['text']));
}
}
} elseif (count($pictures) > 0) {
if (count($pictures) > 4) {
$post['type'] = 'link';
$post['url'] = $plink;
} else {
$post['type'] = 'photo';
}
$post['image'] = $pictures[0][2];
foreach ($pictures as $picture) {
$post['text'] = trim(str_replace($picture[0], '', $post['text']));
}
}
} elseif (preg_match_all("(\[img\](.*?)\[\/img\])ism", $post['text'], $pictures, PREG_SET_ORDER)) {
if ($has_title) {
$post['type'] = 'link';
$post['url'] = $plink;
} else {
$post['type'] = 'photo';
}
$post['image'] = $pictures[0][1];
foreach ($pictures as $picture) {
$post['text'] = trim(str_replace($picture[0], '', $post['text']));
}
}
// Test for the external links
preg_match_all("(\[url\](.*?)\[\/url\])ism", $post['text'], $links1, PREG_SET_ORDER);
preg_match_all("(\[url\=(.*?)\].*?\[\/url\])ism", $post['text'], $links2, PREG_SET_ORDER);
$links = array_merge($links1, $links2);
// If there is only a single one, then use it.
// This should cover link posts via API.
if ((count($links) == 1) && !isset($post['preview']) && !$has_title) {
$post['type'] = 'link';
$post['url'] = $links[0][1];
}
// Simplify "video" element
$post['text'] = preg_replace('(\[video.*?\ssrc\s?=\s?([^\s\]]+).*?\].*?\[/video\])ism', '[video]$1[/video]', $post['text']);
// Now count the number of external media links
preg_match_all("(\[vimeo\](.*?)\[\/vimeo\])ism", $post['text'], $links1, PREG_SET_ORDER);
preg_match_all("(\[youtube\\](.*?)\[\/youtube\\])ism", $post['text'], $links2, PREG_SET_ORDER);
preg_match_all("(\[video\\](.*?)\[\/video\\])ism", $post['text'], $links3, PREG_SET_ORDER);
preg_match_all("(\[audio\\](.*?)\[\/audio\\])ism", $post['text'], $links4, PREG_SET_ORDER);
// Add them to the other external links
$links = array_merge($links, $links1, $links2, $links3, $links4);
// Are there more than one?
if (count($links) > 1) {
// The post will be the type "text", which means a blog post
unset($post['type']);
$post['url'] = $plink;
}
if (!isset($post['type'])) {
$post['type'] = 'text';
}
if (($post['type'] == 'photo') && empty($post['images']) && !empty($post['remote_images'])) {
$post['images'] = $post['remote_images'];
$post['image'] = $post['images'][0]['url'];
if (!empty($post['images']) && !empty($post['images'][0]['description'])) {
$post['image_description'] = $post['images'][0]['description'];
}
}
unset($post['remote_images']);
} elseif (isset($post['url']) && ($post['type'] == 'video')) {
$data = ParseUrl::getSiteinfoCached($post['url']);
if (isset($data['images'][0])) {
$post['image'] = $data['images'][0]['src'];
}
} elseif (preg_match_all("#\[url=([^\]]+?)\]\s*\[img\]([^\[]+?)\[/img\]\s*\[/url\]#ism", $post['text'], $pictures, PREG_SET_ORDER)) {
foreach ($pictures as $picture) {
$post['text'] = trim(str_replace($picture[0], '', $post['text']));
}
}
DI::profiler()->stopRecording();
return $post;
}
/**
* Remove [attachment] BBCode
*

View file

@ -23,7 +23,10 @@ namespace Friendica\Content\Text;
use Friendica\Core\Protocol;
use Friendica\DI;
use Friendica\Model\Photo;
use Friendica\Model\Post;
use Friendica\Util\Network;
use Friendica\Util\Strings;
class Plaintext
{
@ -109,30 +112,15 @@ class Plaintext
* @param int $limit The maximum number of characters when posting to that network
* @param bool $includedlinks Has an attached link to be included into the message?
* @param int $htmlmode This controls the behavior of the BBCode conversion
* @param string $target_network Name of the network where the post should go to.
*
* @return array Same array structure than \Friendica\Content\Text\BBCode::getAttachedData
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @see \Friendica\Content\Text\BBCode::getAttachedData
*/
public static function getPost(array $item, int $limit = 0, bool $includedlinks = false, int $htmlmode = BBCode::MASTODON_API, string $target_network = '')
public static function getPost(array $item, int $limit = 0, bool $includedlinks = false, int $htmlmode = BBCode::MASTODON_API)
{
// Remove hashtags
$URLSearchString = '^\[\]';
$body = preg_replace("/([#@])\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", '$1$3', $item['body']);
// Add an URL element if the text contains a raw link
$body = preg_replace(
'/([^\]\=\'"]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism',
'$1[url]$2[/url]',
$body
);
// Remove the abstract
$body = BBCode::stripAbstract($body);
// At first look at data that is attached via "type-..." stuff
$post = BBCode::getAttachedData($body, $item);
// Fetch attached media information
$post = self::getPostMedia($item);
if (($item['title'] != '') && ($post['text'] != '')) {
$post['text'] = trim($item['title'] . "\n\n" . $post['text']);
@ -140,34 +128,21 @@ class Plaintext
$post['text'] = trim($item['title']);
}
$abstract = '';
// Fetch the abstract from the given target network
if ($target_network != '') {
$default_abstract = BBCode::getAbstract($item['body']);
$abstract = BBCode::getAbstract($item['body'], $target_network);
switch ($htmlmode) {
case BBCode::TWITTER:
$abstract = BBCode::getAbstract($item['body'], Protocol::TWITTER);
break;
// If we post to a network with no limit we only fetch
// an abstract exactly for this network
if (($limit == 0) && ($abstract == $default_abstract)) {
$abstract = '';
}
} else { // Try to guess the correct target network
switch ($htmlmode) {
case BBCode::TWITTER:
$abstract = BBCode::getAbstract($item['body'], Protocol::TWITTER);
break;
case BBCode::OSTATUS:
$abstract = BBCode::getAbstract($item['body'], Protocol::STATUSNET);
break;
case BBCode::OSTATUS:
$abstract = BBCode::getAbstract($item['body'], Protocol::STATUSNET);
break;
default: // We don't know the exact target.
// We fetch an abstract since there is a posting limit.
if ($limit > 0) {
$abstract = BBCode::getAbstract($item['body']);
}
}
default: // We don't know the exact target.
// We fetch an abstract since there is a posting limit.
if ($limit > 0) {
$abstract = BBCode::getAbstract($item['body']);
}
}
if ($abstract != '') {
@ -323,4 +298,87 @@ class Plaintext
return $parts;
}
/**
* Fetch attached media to the post and simplify the body.
*
* @param array $item
* @return array
*/
private static function getPostMedia(array $item): array
{
$post = ['type' => 'text', 'images' => [], 'remote_images' => []];
// Remove mentions and hashtag links
$URLSearchString = '^\[\]';
$post['text'] = preg_replace("/([#!@])\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", '$1$3', $item['body']);
// Remove abstract
$post['text'] = BBCode::stripAbstract($post['text']);
// Remove attached links
$post['text'] = BBCode::removeAttachment($post['text']);
// Remove any links
$post['text'] = Post\Media::removeFromBody($post['text']);
$images = Post\Media::getByURIId($item['uri-id'], [Post\Media::IMAGE]);
if (!empty($item['quote-uri-id'])) {
$images = array_merge($images, Post\Media::getByURIId($item['quote-uri-id'], [Post\Media::IMAGE]));
}
foreach ($images as $image) {
if ($id = Photo::getIdForName($image['url'])) {
$post['images'][] = ['url' => $image['url'], 'description' => $image['description'], 'id' => $id];
} else {
$post['remote_images'][] = ['url' => $image['url'], 'description' => $image['description']];
}
}
if (empty($post['images'])) {
unset($post['images']);
}
if (empty($post['remote_images'])) {
unset($post['remote_images']);
}
if (!empty($post['images'])) {
$post['type'] = 'photo';
$post['image'] = $post['images'][0]['url'];
$post['image_description'] = $post['images'][0]['description'];
} elseif (!empty($post['remote_images'])) {
$post['type'] = 'photo';
$post['image'] = $post['remote_images'][0]['url'];
$post['image_description'] = $post['remote_images'][0]['description'];
}
// Look for audio or video links
$media = Post\Media::getByURIId($item['uri-id'], [Post\Media::AUDIO, Post\Media::VIDEO]);
if (!empty($item['quote-uri-id'])) {
$media = array_merge($media, Post\Media::getByURIId($item['quote-uri-id'], [Post\Media::AUDIO, Post\Media::VIDEO]));
}
foreach ($media as $medium) {
if (in_array($medium['type'], [Post\Media::AUDIO, Post\Media::VIDEO])) {
$post['type'] = 'link';
$post['url'] = $medium['url'];
}
}
// Look for an attached link
$page = Post\Media::getByURIId($item['uri-id'], [Post\Media::HTML]);
if (!empty($item['quote-uri-id']) && empty($page)) {
$page = Post\Media::getByURIId($item['quote-uri-id'], [Post\Media::HTML]);
}
if (!empty($page)) {
$post['type'] = 'link';
$post['url'] = $page[0]['url'];
$post['description'] = $page[0]['description'];
$post['title'] = $page[0]['name'];
if (empty($post['image']) && !empty($page[0]['preview'])) {
$post['image'] = $page[0]['preview'];
}
}
return $post;
}
}

View file

@ -463,7 +463,7 @@ class Media
*/
private static function isPictureLink(string $page, string $preview): bool
{
return preg_match('#/photos/.*/image/#ism', $page) && preg_match('#/photo/.*-1\.#ism', $preview);
return (preg_match('#/photo/.*-0\.#ism', $page) || preg_match('#/photos/.*/image/#ism', $page)) && preg_match('#/photo/.*-[01]\.#ism', $preview);
}
/**
@ -482,15 +482,20 @@ class Media
$attachments = [];
if (preg_match_all("#\[url=([^\]]+?)\]\s*\[img=([^\[\]]*)\]([^\[\]]*)\[\/img\]\s*\[/url\]$endmatchpattern#ism", $body, $pictures, PREG_SET_ORDER)) {
foreach ($pictures as $picture) {
if (!self::isPictureLink($picture[1], $picture[2])) {
continue;
if (self::isPictureLink($picture[1], $picture[2])) {
$body = str_replace($picture[0], '', $body);
$image = str_replace('-1.', '-0.', $picture[2]);
$attachments[$image] = [
'uri-id' => $uriid, 'type' => self::IMAGE, 'url' => $image,
'preview' => $picture[2], 'description' => $picture[3]
];
} else {
$body = str_replace($picture[0], '', $body);
$attachments[$picture[1]] = [
'uri-id' => $uriid, 'type' => self::UNKNOWN, 'url' => $picture[1],
'preview' => $picture[2], 'description' => $picture[3]
];
}
$body = str_replace($picture[0], '', $body);
$image = str_replace('-1.', '-0.', $picture[2]);
$attachments[$image] = [
'uri-id' => $uriid, 'type' => self::IMAGE, 'url' => $image,
'preview' => $picture[2], 'description' => $picture[3]
];
}
}
@ -503,15 +508,20 @@ class Media
if (preg_match_all("#\[url=([^\]]+?)\]\s*\[img\]([^\[]+?)\[/img\]\s*\[/url\]$endmatchpattern#ism", $body, $pictures, PREG_SET_ORDER)) {
foreach ($pictures as $picture) {
if (!self::isPictureLink($picture[1], $picture[2])) {
continue;
if (self::isPictureLink($picture[1], $picture[2])) {
$body = str_replace($picture[0], '', $body);
$image = str_replace('-1.', '-0.', $picture[2]);
$attachments[$image] = [
'uri-id' => $uriid, 'type' => self::IMAGE, 'url' => $image,
'preview' => $picture[2], 'description' => null
];
} else {
$body = str_replace($picture[0], '', $body);
$attachments[$picture[1]] = [
'uri-id' => $uriid, 'type' => self::UNKNOWN, 'url' => $picture[1],
'preview' => $picture[2], 'description' => null
];
}
$body = str_replace($picture[0], '', $body);
$image = str_replace('-1.', '-0.', $picture[2]);
$attachments[$image] = [
'uri-id' => $uriid, 'type' => self::IMAGE, 'url' => $image,
'preview' => $picture[2], 'description' => null
];
}
}
@ -567,6 +577,21 @@ class Media
return $body;
}
/**
* Remove media from the body
*
* @param string $body
* @return string
*/
public static function removeFromBody(string $body): string
{
do {
$prebody = $body;
$body = self::insertFromBody(0, $body);
} while ($prebody != $body);
return $body;
}
/**
* Add media links from a relevant url in the body
*

View file

@ -21,6 +21,7 @@
namespace Friendica\Module\Api\Mastodon;
use Friendica\Content\PageInfo;
use Friendica\Content\Text\BBCode;
use Friendica\Content\Text\Markdown;
use Friendica\Core\Protocol;
@ -37,6 +38,7 @@ use Friendica\Model\User;
use Friendica\Module\BaseApi;
use Friendica\Network\HTTPException;
use Friendica\Protocol\Activity;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Images;
/**
@ -166,6 +168,10 @@ class Statuses extends BaseApi
// The imput is defined as text. So we can use Markdown for some enhancements
$body = Markdown::toBBCode($request['status']);
if (DI::pConfig()->get($uid, 'system', 'api_auto_attach', false) && preg_match("/\[url=[^\[\]]*\](.*)\[\/url\]\z/ism", $body, $matches)) {
$body = preg_replace("/\[url=[^\[\]]*\].*\[\/url\]\z/ism", PageInfo::getFooterFromUrl($matches[1]), $body);
}
$item = [];
$item['network'] = Protocol::DFRN;
$item['uid'] = $uid;
@ -279,7 +285,7 @@ class Statuses extends BaseApi
if (!empty($request['scheduled_at'])) {
$item['guid'] = Item::guid($item, true);
$item['uri'] = Item::newURI($item['guid']);
$id = Post\Delayed::add($item['uri'], $item, Worker::PRIORITY_HIGH, Post\Delayed::PREPARED, $request['scheduled_at']);
$id = Post\Delayed::add($item['uri'], $item, Worker::PRIORITY_HIGH, Post\Delayed::PREPARED, DateTimeFormat::utc($request['scheduled_at']));
if (empty($id)) {
DI::mstdnError()->InternalError();
}

View file

@ -73,6 +73,7 @@ class Connectors extends BaseSettings
$this->pconfig->set($this->session->getLocalUserId(), 'system', 'simple_shortening', intval($request['simple_shortening']));
$this->pconfig->set($this->session->getLocalUserId(), 'system', 'attach_link_title', intval($request['attach_link_title']));
$this->pconfig->set($this->session->getLocalUserId(), 'system', 'api_spoiler_title', intval($request['api_spoiler_title']));
$this->pconfig->set($this->session->getLocalUserId(), 'system', 'api_auto_attach', intval($request['api_auto_attach']));
$this->pconfig->set($this->session->getLocalUserId(), 'ostatus', 'legacy_contact', $request['legacy_contact']);
} elseif (!empty($request['mail-submit']) && function_exists('imap_open') && !$this->config->get('system', 'imap_disabled')) {
$mail_server = $request['mail_server'] ?? '';
@ -136,6 +137,7 @@ class Connectors extends BaseSettings
$simple_shortening = intval($this->pconfig->get($this->session->getLocalUserId(), 'system', 'simple_shortening'));
$attach_link_title = intval($this->pconfig->get($this->session->getLocalUserId(), 'system', 'attach_link_title'));
$api_spoiler_title = intval($this->pconfig->get($this->session->getLocalUserId(), 'system', 'api_spoiler_title', true));
$api_auto_attach = intval($this->pconfig->get($this->session->getLocalUserId(), 'system', 'api_auto_attach', false));
$legacy_contact = $this->pconfig->get($this->session->getLocalUserId(), 'ostatus', 'legacy_contact');
if (!empty($legacy_contact)) {
@ -221,6 +223,7 @@ class Connectors extends BaseSettings
'$simple_shortening' => ['simple_shortening', $this->t('Enable simple text shortening'), $simple_shortening, $this->t('Normally the system shortens posts at the next line feed. If this option is enabled then the system will shorten the text at the maximum character limit.')],
'$attach_link_title' => ['attach_link_title', $this->t('Attach the link title'), $attach_link_title, $this->t('When activated, the title of the attached link will be added as a title on posts to Diaspora. This is mostly helpful with "remote-self" contacts that share feed content.')],
'$api_spoiler_title' => ['api_spoiler_title', $this->t('API: Use spoiler field as title'), $api_spoiler_title, $this->t('When activated, the "spoiler_text" field in the API will be used for the title on standalone posts. When deactivated it will be used for spoiler text. For comments it will always be used for spoiler text.')],
'$api_auto_attach' => ['api_auto_attach', $this->t('API: Automatically links at the end of the post as attached posts'), $api_auto_attach, $this->t('When activated, added links at the end of the post react the same way as added links in the web interface.')],
'$legacy_contact' => ['legacy_contact', $this->t('Your legacy ActivityPub/GNU Social account'), $legacy_contact, $this->t('If you enter your old account name from an ActivityPub based system or your GNU Social/Statusnet account name here (in the format user@domain.tld), your contacts will be added automatically. The field will be emptied when done.')],
'$repair_ostatus_url' => 'ostatus/repair',
'$repair_ostatus_text' => $this->t('Repair OStatus subscriptions'),

View file

@ -213,6 +213,10 @@ class Status extends BaseDataTransferObject
$status['in_reply_to_status'] = null;
}
if ($status['created_at'] == $status['edited_at']) {
$status['edited_at'] = null;
}
return $status;
}
}

File diff suppressed because it is too large Load diff

View file

@ -17,6 +17,7 @@
{{include file="field_checkbox.tpl" field=$simple_shortening}}
{{include file="field_checkbox.tpl" field=$attach_link_title}}
{{include file="field_checkbox.tpl" field=$api_spoiler_title}}
{{include file="field_checkbox.tpl" field=$api_auto_attach}}
{{include file="field_input.tpl" field=$legacy_contact}}
<p><a href="{{$repair_ostatus_url}}">{{$repair_ostatus_text}}</a></p>

View file

@ -22,6 +22,10 @@
</div>
</ol>
<div class="upload">
<button id="upload-{{$type}}" type="button" class="btn btn-primary">{{$upload}}</button>
</div>
<div class="media">
{{* List of photo albums *}}
@ -52,9 +56,6 @@
</div>
</div>
<div class="upload">
<button id="upload-{{$type}}" type="button" class="btn btn-primary">{{$upload}}</button>
</div>
</div>
{{* This part contains the conent loader icon which is visible when new conent is loaded *}}

View file

@ -30,6 +30,8 @@
{{include file="field_checkbox.tpl" field=$api_spoiler_title}}
{{include file="field_checkbox.tpl" field=$api_auto_attach}}
{{include file="field_input.tpl" field=$legacy_contact}}
<p><a href="{{$repair_ostatus_url}}">{{$repair_ostatus_text}}</a></p>