Merge pull request #13635 from gudzpoz/emojis-please
Improve emoji federation (ActivityPub) and Mastodon API compliance
This commit is contained in:
commit
21227453e5
|
@ -21,6 +21,7 @@
|
|||
|
||||
namespace Friendica\Content;
|
||||
|
||||
use Friendica\Content\Text\BBCode;
|
||||
use Friendica\Core\Hook;
|
||||
use Friendica\DI;
|
||||
use Friendica\Util\Strings;
|
||||
|
@ -67,7 +68,7 @@ class Smilies
|
|||
*/
|
||||
public static function getList(): array
|
||||
{
|
||||
$texts = [
|
||||
$texts = [
|
||||
'<3',
|
||||
'</3',
|
||||
'<\\3',
|
||||
|
@ -152,6 +153,155 @@ class Smilies
|
|||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes smiley shortcodes into texts with no special symbols.
|
||||
*
|
||||
* @return array
|
||||
* 'texts' => smilie shortcut
|
||||
* 'icons' => icon url or an empty string
|
||||
* 'norms' => normalized shortcut
|
||||
*/
|
||||
public static function getNormalizedList(): array
|
||||
{
|
||||
$smilies = self::getList();
|
||||
$norms = [];
|
||||
$icons = $smilies['icons'];
|
||||
foreach ($smilies['texts'] as $i => $shortcode) {
|
||||
// Extract urls
|
||||
$icon = $icons[$i];
|
||||
if (preg_match('/src="(.+?)"/', $icon, $match)) {
|
||||
$icon = $match[1];
|
||||
} else {
|
||||
$icon = '';
|
||||
}
|
||||
$icons[$i] = $icon;
|
||||
|
||||
// Normalize name
|
||||
$norm = preg_replace('/[\s\-:#~]/', '', $shortcode);
|
||||
if (ctype_alnum($norm)) {
|
||||
$norms[] = $norm;
|
||||
} elseif (preg_match('#/smiley-(\w+)\.gif#', $icon, $match)) {
|
||||
$norms[] = $match[1];
|
||||
} else {
|
||||
$norms[] = 'smiley' . $i;
|
||||
}
|
||||
}
|
||||
$smilies['norms'] = $norms;
|
||||
return $smilies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all used smilies (denoted by quoting colons like :heart:) in the provided text and normalizes their usages.
|
||||
*
|
||||
* @param string $text that might contain smiley usages
|
||||
* @return array with smilie codes (colon included) as the keys, their image urls as values;
|
||||
* the normalized string is put under the '' (empty string) key
|
||||
*/
|
||||
public static function extractUsedSmilies(string $text, string &$normalized = null): array
|
||||
{
|
||||
$emojis = [];
|
||||
|
||||
$normalized = BBCode::performWithEscapedTags($text, ['code'], function ($text) use (&$emojis) {
|
||||
return BBCode::performWithEscapedTags($text, ['noparse', 'nobb', 'pre'], function ($text) use (&$emojis) {
|
||||
if (strpos($text, '[nosmile]') !== false || self::noSmilies()) {
|
||||
return $text;
|
||||
}
|
||||
$smilies = self::getNormalizedList();
|
||||
$normalized = array_combine($smilies['texts'], $smilies['norms']);
|
||||
return self::performForEachWordMatch(
|
||||
array_combine($smilies['texts'], $smilies['icons']),
|
||||
$text,
|
||||
function (string $name, string $image) use($normalized, &$emojis) {
|
||||
$name = $normalized[$name];
|
||||
if (preg_match('/src="(.+?)"/', $image, $match)) {
|
||||
$image = $match[1];
|
||||
$emojis[$name] = $image;
|
||||
}
|
||||
return ':' . $name . ':';
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
return $emojis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to strtr but matches only whole words and replaces texts with $callback.
|
||||
*
|
||||
* @param array $words
|
||||
* @param string $subject
|
||||
* @param callable $callback ($offset, $value)
|
||||
* @return string
|
||||
*/
|
||||
private static function performForEachWordMatch(array $words, string $subject, callable $callback): string
|
||||
{
|
||||
$ord1_bitset = 0;
|
||||
$ord2_bitset = 0;
|
||||
$prefixes = [];
|
||||
foreach ($words as $word => $_) {
|
||||
if (strlen($word) < 2 || !ctype_graph($word)) {
|
||||
continue;
|
||||
}
|
||||
$ord1 = ord($word);
|
||||
$ord2 = ord($word[1]);
|
||||
$ord1_bitset |= 1 << ($ord1 & 31);
|
||||
$ord2_bitset |= 1 << ($ord2 & 31);
|
||||
if (!array_key_exists($word[0], $prefixes)) {
|
||||
$prefixes[$word[0]] = [];
|
||||
}
|
||||
$prefixes[$word[0]][] = $word;
|
||||
}
|
||||
|
||||
$result = '';
|
||||
$processed = 0;
|
||||
$s_start = 0; // Segment start
|
||||
// No spaces are allowed in smilies, so they can serve as delimiters.
|
||||
// Splitting by some delimiters may not necessary though?
|
||||
while (true) {
|
||||
if ($s_start >= strlen($subject)) {
|
||||
$result .= substr($subject, $processed);
|
||||
break;
|
||||
}
|
||||
if (preg_match('/\s+?(?=\S|$)/', $subject, $match, PREG_OFFSET_CAPTURE, $s_start)) {
|
||||
[$whitespaces, $s_end] = $match[0];
|
||||
} else {
|
||||
$s_end = strlen($subject);
|
||||
$whitespaces = '';
|
||||
}
|
||||
$s_length = $s_end - $s_start;
|
||||
if ($s_length > 1) {
|
||||
$segment = substr($subject, $s_start, $s_length);
|
||||
// Find possible starting points for smilies.
|
||||
// For built-in smilies, the two bitsets should make attempts quite efficient.
|
||||
// However, presuming custom smilies follow the format of ":shortcode" or ":shortcode:",
|
||||
// if the user adds more smilies (with addons), the second bitset may eventually become useless.
|
||||
for ($i = 0; $i < $s_length - 1; $i++) {
|
||||
$c = $segment[$i];
|
||||
$d = $segment[$i + 1];
|
||||
if (($ord1_bitset & (1 << (ord($c) & 31))) && ($ord2_bitset & (1 << (ord($d) & 31))) && array_key_exists($c, $prefixes)) {
|
||||
foreach ($prefixes[$c] as $word) {
|
||||
$wlength = strlen($word);
|
||||
if ($wlength <= $s_length - $i && substr($segment, $i, $wlength) === $word) {
|
||||
// Check for boundaries
|
||||
if (($i === 0 || ctype_space($segment[$i - 1]) || ctype_punct($segment[$i - 1]))
|
||||
&& ($i + $wlength >= $s_length || ctype_space($segment[$i + $wlength]) || ctype_punct($segment[$i + $wlength]))) {
|
||||
$result .= substr($subject, $processed, $s_start - $processed + $i);
|
||||
$result .= call_user_func($callback, $word, $words[$word]);
|
||||
$i += $wlength;
|
||||
$processed = $s_start + $i;
|
||||
$i--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$s_start = $s_end + strlen($whitespaces);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copied from http://php.net/manual/en/function.str-replace.php#88569
|
||||
|
@ -170,7 +320,13 @@ class Smilies
|
|||
*/
|
||||
private static function strOrigReplace(array $search, array $replace, string $subject): string
|
||||
{
|
||||
return strtr($subject, array_combine($search, $replace));
|
||||
return self::performForEachWordMatch(
|
||||
array_combine($search, $replace),
|
||||
$subject,
|
||||
function (string $_, string $value) {
|
||||
return $value;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -199,6 +355,12 @@ class Smilies
|
|||
return $s;
|
||||
}
|
||||
|
||||
private static function noSmilies(): bool {
|
||||
return (intval(DI::config()->get('system', 'no_smilies')) ||
|
||||
(DI::userSession()->getLocalUserId() &&
|
||||
intval(DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'no_smilies'))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces emoji shortcodes in a string from a structured array of searches and replaces.
|
||||
*
|
||||
|
@ -212,9 +374,7 @@ class Smilies
|
|||
*/
|
||||
public static function replaceFromArray(string $text, array $smilies, bool $no_images = false): string
|
||||
{
|
||||
if (intval(DI::config()->get('system', 'no_smilies'))
|
||||
|| (DI::userSession()->getLocalUserId() && intval(DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'no_smilies')))
|
||||
) {
|
||||
if (self::noSmilies()) {
|
||||
return $text;
|
||||
}
|
||||
|
||||
|
@ -233,7 +393,7 @@ class Smilies
|
|||
$smilies = $cleaned;
|
||||
}
|
||||
|
||||
$text = preg_replace_callback('/<(3+)/', [self::class, 'heartReplaceCallback'], $text);
|
||||
$text = preg_replace_callback('/\B<3+?\b/', [self::class, 'heartReplaceCallback'], $text);
|
||||
$text = self::strOrigReplace($smilies['texts'], $smilies['icons'], $text);
|
||||
|
||||
$text = preg_replace_callback('/<(code)>(.*?)<\/code>/ism', [self::class, 'decode'], $text);
|
||||
|
@ -274,16 +434,7 @@ class Smilies
|
|||
*/
|
||||
private static function heartReplaceCallback(array $matches): string
|
||||
{
|
||||
if (strlen($matches[1]) == 1) {
|
||||
return $matches[0];
|
||||
}
|
||||
|
||||
$t = '';
|
||||
for ($cnt = 0; $cnt < strlen($matches[1]); $cnt ++) {
|
||||
$t .= '❤';
|
||||
}
|
||||
|
||||
return str_replace($matches[0], $t, $matches[0]);
|
||||
return str_repeat('❤', strlen($matches[0]) - 4);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1234,7 +1234,7 @@ class BBCode
|
|||
}
|
||||
|
||||
/**
|
||||
* Expand Youtube and Vimeo links to
|
||||
* Expand Youtube and Vimeo links to
|
||||
*
|
||||
* @param string $text
|
||||
* @return string
|
||||
|
@ -1387,7 +1387,7 @@ class BBCode
|
|||
"\n[hr]", "[hr]\n", " [hr]", "[hr] ",
|
||||
"\n[attachment ", " [attachment ", "\n[/attachment]", "[/attachment]\n", " [/attachment]", "[/attachment] ",
|
||||
"[table]\n", "[table] ", " [table]", "\n[/table]", " [/table]", "[/table] ",
|
||||
" \n", "\t\n", "[/li]\n", "\n[li]", "\n[*]",
|
||||
" \n", "\t\n", "[/li]\n", "\n[li]", "\n[*]",
|
||||
];
|
||||
$replace = [
|
||||
"[th]", "[th]", "[th]", "[/th]", "[/th]", "[/th]",
|
||||
|
@ -1480,14 +1480,14 @@ class BBCode
|
|||
if ($simple_html == self::INTERNAL) {
|
||||
//Ensure to always start with <h4> if possible
|
||||
$heading_count = 0;
|
||||
for ($level = 6; $level > 0; $level--) {
|
||||
for ($level = 6; $level > 0; $level--) {
|
||||
if (preg_match("(\[h$level\].*?\[\/h$level\])ism", $text)) {
|
||||
$heading_count++;
|
||||
}
|
||||
}
|
||||
if ($heading_count > 0) {
|
||||
$heading = min($heading_count + 3, 6);
|
||||
for ($level = 6; $level > 0; $level--) {
|
||||
for ($level = 6; $level > 0; $level--) {
|
||||
if (preg_match("(\[h$level\].*?\[\/h$level\])ism", $text)) {
|
||||
$text = preg_replace("(\[h$level\](.*?)\[\/h$level\])ism", "</p><h$heading>$1</h$heading><p>", $text);
|
||||
$heading--;
|
||||
|
@ -1548,7 +1548,11 @@ class BBCode
|
|||
$text = preg_replace("(\[style=(.*?)\](.*?)\[\/style\])ism", '<span style="$1">$2</span>', $text);
|
||||
|
||||
// Mastodon Emoji (internal tag, do not document for users)
|
||||
$text = preg_replace("(\[emoji=(.*?)](.*?)\[/emoji])ism", '<span class="mastodon emoji"><img src="$1" alt="$2" title="$2"/></span>', $text);
|
||||
if ($simple_html == self::MASTODON_API) {
|
||||
$text = preg_replace("(\[emoji=(.*?)](.*?)\[/emoji])ism", '$2', $text);
|
||||
} else {
|
||||
$text = preg_replace("(\[emoji=(.*?)](.*?)\[/emoji])ism", '<span class="mastodon emoji"><img src="$1" alt="$2" title="$2"/></span>', $text);
|
||||
}
|
||||
|
||||
// Check for CSS classes
|
||||
// @deprecated since 2021.12, left for backward-compatibility reasons
|
||||
|
|
|
@ -32,19 +32,21 @@ class Emoji extends BaseFactory
|
|||
}
|
||||
|
||||
/**
|
||||
* Creates an emoji collection from shortcode => image mappings.
|
||||
*
|
||||
* @param array $smilies
|
||||
*
|
||||
* @return Emojis
|
||||
*/
|
||||
public function createCollectionFromSmilies(array $smilies): Emojis
|
||||
public function createCollectionFromArray(array $smilies): Emojis
|
||||
{
|
||||
$prototype = null;
|
||||
|
||||
$emojis = [];
|
||||
|
||||
foreach ($smilies['texts'] as $key => $shortcode) {
|
||||
if (preg_match('/src="(.+?)"/', $smilies['icons'][$key], $matches)) {
|
||||
$url = $matches[1];
|
||||
foreach ($smilies as $shortcode => $url) {
|
||||
if ($shortcode !== '' && $url !== '') {
|
||||
$shortcode = trim($shortcode, ':');
|
||||
|
||||
if ($prototype === null) {
|
||||
$prototype = $this->create($shortcode, $url);
|
||||
|
@ -52,9 +54,27 @@ class Emoji extends BaseFactory
|
|||
} else {
|
||||
$emojis[] = \Friendica\Object\Api\Mastodon\Emoji::createFromPrototype($prototype, $shortcode, $url);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return new Emojis($emojis);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $smilies as is returned by Smilies::getList()
|
||||
*
|
||||
* @return Emojis
|
||||
*/
|
||||
public function createCollectionFromSmilies(array $smilies): Emojis
|
||||
{
|
||||
$emojis = [];
|
||||
$icons = $smilies['icons'];
|
||||
foreach ($smilies['texts'] as $i => $name) {
|
||||
$url = $icons[$i];
|
||||
if (preg_match('/src="(.+?)"/', $url, $matches)) {
|
||||
$emojis[$name] = $matches[1];
|
||||
}
|
||||
}
|
||||
return self::createCollectionFromArray($emojis);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ namespace Friendica\Factory\Api\Mastodon;
|
|||
use Friendica\BaseFactory;
|
||||
use Friendica\Content\ContactSelector;
|
||||
use Friendica\Content\Item as ContentItem;
|
||||
use Friendica\Content\Smilies;
|
||||
use Friendica\Content\Text\BBCode;
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Database\Database;
|
||||
|
@ -57,6 +58,8 @@ class Status extends BaseFactory
|
|||
private $mstdnCardFactory;
|
||||
/** @var Attachment */
|
||||
private $mstdnAttachmentFactory;
|
||||
/** @var Emoji */
|
||||
private $mstdnEmojiFactory;
|
||||
/** @var Error */
|
||||
private $mstdnErrorFactory;
|
||||
/** @var Poll */
|
||||
|
@ -74,6 +77,7 @@ class Status extends BaseFactory
|
|||
Tag $mstdnTagFactory,
|
||||
Card $mstdnCardFactory,
|
||||
Attachment $mstdnAttachmentFactory,
|
||||
Emoji $mstdnEmojiFactory,
|
||||
Error $mstdnErrorFactory,
|
||||
Poll $mstdnPollFactory,
|
||||
ContentItem $contentItem,
|
||||
|
@ -86,6 +90,7 @@ class Status extends BaseFactory
|
|||
$this->mstdnTagFactory = $mstdnTagFactory;
|
||||
$this->mstdnCardFactory = $mstdnCardFactory;
|
||||
$this->mstdnAttachmentFactory = $mstdnAttachmentFactory;
|
||||
$this->mstdnEmojiFactory = $mstdnEmojiFactory;
|
||||
$this->mstdnErrorFactory = $mstdnErrorFactory;
|
||||
$this->mstdnPollFactory = $mstdnPollFactory;
|
||||
$this->contentItem = $contentItem;
|
||||
|
@ -283,6 +288,21 @@ class Status extends BaseFactory
|
|||
}
|
||||
}
|
||||
|
||||
$emojis = null;
|
||||
if (DI::baseUrl()->isLocalUrl($item['uri'])) {
|
||||
$used_smilies = Smilies::extractUsedSmilies($item['raw-body'] ?: $item['body'], $normalized);
|
||||
if ($item['raw-body']) {
|
||||
$item['raw-body'] = $normalized;
|
||||
} elseif ($item['body']) {
|
||||
$item['body'] = $normalized;
|
||||
}
|
||||
$emojis = $this->mstdnEmojiFactory->createCollectionFromArray($used_smilies)->getArrayCopy(true);
|
||||
} else {
|
||||
if (preg_match_all("(\[emoji=(.*?)](.*?)\[/emoji])ism", $item['body'] ?: $item['raw-body'], $matches)) {
|
||||
$emojis = $this->mstdnEmojiFactory->createCollectionFromArray(array_combine($matches[2], $matches[1]))->getArrayCopy(true);
|
||||
}
|
||||
}
|
||||
|
||||
if ($is_reshare) {
|
||||
try {
|
||||
$reshare = $this->createFromUriId($uriId, $uid, $display_quote, false, false)->toArray();
|
||||
|
@ -309,7 +329,7 @@ class Status extends BaseFactory
|
|||
$visibility_data = $uid != $item['uid'] ? null : new FriendicaVisibility($this->aclFormatter->expand($item['allow_cid']), $this->aclFormatter->expand($item['deny_cid']), $this->aclFormatter->expand($item['allow_gid']), $this->aclFormatter->expand($item['deny_gid']));
|
||||
$friendica = new FriendicaExtension($item['title'] ?? '', $item['changed'], $item['commented'], $item['received'], $counts->dislikes, $origin_dislike, $delivery_data, $visibility_data);
|
||||
|
||||
return new \Friendica\Object\Api\Mastodon\Status($item, $account, $counts, $userAttributes, $sensitive, $application, $mentions, $tags, $card, $attachments, $in_reply, $reshare, $friendica, $quote, $poll);
|
||||
return new \Friendica\Object\Api\Mastodon\Status($item, $account, $counts, $userAttributes, $sensitive, $application, $mentions, $tags, $card, $attachments, $in_reply, $reshare, $friendica, $quote, $poll, $emojis);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -107,7 +107,7 @@ class Status extends BaseDataTransferObject
|
|||
* @param array $item
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public function __construct(array $item, Account $account, Counts $counts, UserAttributes $userAttributes, bool $sensitive, Application $application, array $mentions, array $tags, Card $card, array $attachments, array $in_reply, array $reblog, FriendicaExtension $friendica, array $quote = null, array $poll = null)
|
||||
public function __construct(array $item, Account $account, Counts $counts, UserAttributes $userAttributes, bool $sensitive, Application $application, array $mentions, array $tags, Card $card, array $attachments, array $in_reply, array $reblog, FriendicaExtension $friendica, array $quote = null, array $poll = null, array $emojis = null)
|
||||
{
|
||||
$reblogged = !empty($reblog);
|
||||
$this->id = (string)$item['uri-id'];
|
||||
|
@ -152,7 +152,7 @@ class Status extends BaseDataTransferObject
|
|||
$this->media_attachments = $reblogged ? [] : $attachments;
|
||||
$this->mentions = $reblogged ? [] : $mentions;
|
||||
$this->tags = $reblogged ? [] : $tags;
|
||||
$this->emojis = $reblogged ? [] : [];
|
||||
$this->emojis = $reblogged ? [] : ($emojis ?: []);
|
||||
$this->card = $reblogged ? null : ($card->toArray() ?: null);
|
||||
$this->poll = $reblogged ? null : $poll;
|
||||
$this->friendica = $reblogged ? null : $friendica;
|
||||
|
|
|
@ -23,6 +23,7 @@ namespace Friendica\Protocol\ActivityPub;
|
|||
|
||||
use Friendica\App;
|
||||
use Friendica\Content\Feature;
|
||||
use Friendica\Content\Smilies;
|
||||
use Friendica\Content\Text\BBCode;
|
||||
use Friendica\Core\Cache\Enum\Duration;
|
||||
use Friendica\Core\Logger;
|
||||
|
@ -898,7 +899,7 @@ class Transmitter
|
|||
$tags = Tag::getByURIId($uri_id, [Tag::TO, Tag::CC, Tag::BCC, Tag::AUDIENCE]);
|
||||
if (empty($tags)) {
|
||||
Logger::debug('No receivers found', ['uri-id' => $uri_id]);
|
||||
$post = Post::selectFirst([Item::DELIVER_FIELDLIST], ['uri-id' => $uri_id, 'origin' => true]);
|
||||
$post = Post::selectFirst(Item::DELIVER_FIELDLIST, ['uri-id' => $uri_id, 'origin' => true]);
|
||||
if (!empty($post)) {
|
||||
ActivityPub\Transmitter::storeReceiversForItem($post);
|
||||
$tags = Tag::getByURIId($uri_id, [Tag::TO, Tag::CC, Tag::BCC, Tag::AUDIENCE]);
|
||||
|
@ -1506,6 +1507,29 @@ class Transmitter
|
|||
return $location;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends emoji tags to a tag array according to the tags used.
|
||||
*
|
||||
* @param array $tags Tag array
|
||||
* @param string $text Text containing tags like :tag:
|
||||
* @return string normalized text
|
||||
*/
|
||||
private static function addEmojiTags(array &$tags, string $text): string
|
||||
{
|
||||
$emojis = Smilies::extractUsedSmilies($text, $normalized);
|
||||
foreach ($emojis as $name => $url) {
|
||||
$tags[] = [
|
||||
'type' => 'Emoji',
|
||||
'name' => $name,
|
||||
'icon' => [
|
||||
'type' => 'Image',
|
||||
'url' => $url,
|
||||
],
|
||||
];
|
||||
}
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a tag array for a given item array
|
||||
*
|
||||
|
@ -1785,10 +1809,11 @@ class Transmitter
|
|||
$item = Post\Media::addHTMLAttachmentToItem($item);
|
||||
|
||||
$body = $item['body'];
|
||||
|
||||
$emojis = [];
|
||||
if ($type == 'Note') {
|
||||
$body = $item['raw-body'] ?? self::removePictures($body);
|
||||
}
|
||||
$body = self::addEmojiTags($emojis, $body);
|
||||
|
||||
/**
|
||||
* @todo Improve the automated summary
|
||||
|
@ -1870,7 +1895,7 @@ class Transmitter
|
|||
}
|
||||
|
||||
$data['attachment'] = self::createAttachmentList($item);
|
||||
$data['tag'] = self::createTagList($item, $data['quoteUrl'] ?? '');
|
||||
$data['tag'] = array_merge(self::createTagList($item, $data['quoteUrl'] ?? ''), $emojis);
|
||||
|
||||
if (empty($data['location']) && (!empty($item['coord']) || !empty($item['location']))) {
|
||||
$data['location'] = self::createLocation($item);
|
||||
|
|
|
@ -112,6 +112,11 @@ return [
|
|||
'uri' => 'http://localhost/profile/mutualcontact',
|
||||
'guid' => '46',
|
||||
],
|
||||
[
|
||||
'id' => 100,
|
||||
'uri' => 'https://friendica.local/posts/100',
|
||||
'guid' => '100',
|
||||
],
|
||||
],
|
||||
'contact' => [
|
||||
[
|
||||
|
@ -363,6 +368,12 @@ return [
|
|||
'et sed beatae nihil ullam temporibus corporis ratione blanditiis',
|
||||
'plink' => 'http://localhost/display/6',
|
||||
],
|
||||
[
|
||||
'uri-id' => 100,
|
||||
'title' => 'item_title',
|
||||
'body' => ':like ~friendica no [code]:dislike[/code] :-p :-[ <3',
|
||||
'plink' => 'https://friendica.local/post/100',
|
||||
],
|
||||
],
|
||||
'post' => [
|
||||
[
|
||||
|
@ -744,6 +755,28 @@ return [
|
|||
'deleted' => 0,
|
||||
'wall' => 0,
|
||||
],
|
||||
// An emoji post
|
||||
[
|
||||
'id' => 14,
|
||||
'uri-id' => 100,
|
||||
'visible' => 1,
|
||||
'contact-id' => 44,
|
||||
'author-id' => 44,
|
||||
'owner-id' => 42,
|
||||
'causer-id' => 44,
|
||||
'uid' => 0,
|
||||
'vid' => 8,
|
||||
'unseen' => 0,
|
||||
'parent-uri-id' => 7,
|
||||
'thr-parent-id' => 7,
|
||||
'private' => Item::PUBLIC,
|
||||
'global' => true,
|
||||
'gravity' => Item::GRAVITY_PARENT,
|
||||
'network' => Protocol::DFRN,
|
||||
'origin' => 0,
|
||||
'deleted' => 0,
|
||||
'wall' => 0,
|
||||
],
|
||||
],
|
||||
'post-thread' => [
|
||||
[
|
||||
|
|
|
@ -143,4 +143,126 @@ class SmiliesTest extends FixtureTest
|
|||
{
|
||||
$this->assertEquals($expected, Smilies::isEmojiPost($body));
|
||||
}
|
||||
|
||||
|
||||
public function dataReplace(): array
|
||||
{
|
||||
$data = [
|
||||
'simple-1' => [
|
||||
'expected' => 'alt=":-p"',
|
||||
'body' => ':-p',
|
||||
],
|
||||
'simple-1' => [
|
||||
'expected' => 'alt=":-p"',
|
||||
'body' => ' :-p ',
|
||||
],
|
||||
'word-boundary-1' => [
|
||||
'expected' => ':-pppp',
|
||||
'body' => ':-pppp',
|
||||
],
|
||||
'word-boundary-2' => [
|
||||
'expected' => '~friendicaca',
|
||||
'body' => '~friendicaca',
|
||||
],
|
||||
'symbol-boundary-1' => [
|
||||
'expected' => 'alt=":-p"',
|
||||
'body' => '(:-p)',
|
||||
],
|
||||
'hearts-1' => [
|
||||
'expected' => '❤ (❤) ❤',
|
||||
'body' => '<3 (<3) <3',
|
||||
],
|
||||
'hearts-8' => [
|
||||
'expected' => '(❤❤❤❤❤❤❤❤)',
|
||||
'body' => '(<33333333)',
|
||||
],
|
||||
'no-hearts-1' => [
|
||||
'expected' => '(<30)',
|
||||
'body' => '(<30)',
|
||||
],
|
||||
'no-hearts-2' => [
|
||||
'expected' => '(3<33)',
|
||||
'body' => '(3<33)',
|
||||
],
|
||||
];
|
||||
foreach ([':-[', ':-D', 'o.O'] as $emoji) {
|
||||
foreach (['A', '_', ':', '-'] as $prefix) {
|
||||
foreach (['', ' ', 'A', ':', '-'] as $suffix) {
|
||||
$no_smile = ($prefix !== '' && ctype_alnum($prefix)) || ($suffix !== '' && ctype_alnum($suffix));
|
||||
$s = $prefix . $emoji . $suffix;
|
||||
$data[] = [
|
||||
'expected' => $no_smile ? $s : 'alt="' . $emoji . '"',
|
||||
'body' => $s,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataReplace
|
||||
*
|
||||
* @param string $expected
|
||||
* @param string $body
|
||||
*/
|
||||
public function testReplace(string $expected, string $body)
|
||||
{
|
||||
$result = Smilies::replace($body);
|
||||
$this->assertStringContainsString($expected, $result);
|
||||
}
|
||||
|
||||
public function dataExtractUsedSmilies(): array
|
||||
{
|
||||
return [
|
||||
'symbols' => [
|
||||
'expected' => ['p', 'heart', 'embarrassed', 'kiss'],
|
||||
'body' => ':-p <3 ":-[:-"',
|
||||
'normalized' => ':p: :heart: ":embarrassed::kiss:',
|
||||
],
|
||||
'single-smiley' => [
|
||||
'expected' => ['like'],
|
||||
'body' => ':like',
|
||||
'normalized' => ':like:',
|
||||
],
|
||||
'multiple-smilies' => [
|
||||
'expected' => ['like', 'dislike'],
|
||||
'body' => ':like :dislike',
|
||||
'normalized' => ':like: :dislike:',
|
||||
],
|
||||
'nosmile' => [
|
||||
'expected' => [],
|
||||
'body' => '[nosmile] :like :like',
|
||||
'normalized' => '[nosmile] :like :like'
|
||||
],
|
||||
'in-code' => [
|
||||
'expected' => [],
|
||||
'body' => '[code]:like :like :like[/code]',
|
||||
'normalized' => '[code]:like :like :like[/code]'
|
||||
],
|
||||
'~friendica' => [
|
||||
'expected' => ['friendica'],
|
||||
'body' => '~friendica',
|
||||
'normalized' => ':friendica:'
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataExtractUsedSmilies
|
||||
*
|
||||
* @param array $expected
|
||||
* @param string $body
|
||||
* @param stirng $normalized
|
||||
*/
|
||||
public function testExtractUsedSmilies(array $expected, string $body, string $normalized)
|
||||
{
|
||||
$extracted = Smilies::extractUsedSmilies($body, $converted);
|
||||
$expected = array_fill_keys($expected, true);
|
||||
$this->assertEquals($normalized, $converted);
|
||||
foreach (array_keys($extracted) as $shortcode) {
|
||||
$this->assertArrayHasKey($shortcode, $expected);
|
||||
}
|
||||
$this->assertEquals(count($expected), count($extracted));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2023, the Friendica project
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Friendica\Test\src\Factory\Api\Mastodon;
|
||||
|
||||
use Friendica\Content\Smilies;
|
||||
use Friendica\DI;
|
||||
use Friendica\Test\FixtureTest;
|
||||
|
||||
class EmojiTest extends FixtureTest
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
DI::config()->set('system', 'no_smilies', false);
|
||||
}
|
||||
|
||||
public function testBuiltInCollection()
|
||||
{
|
||||
$emoji = DI::mstdnEmoji();
|
||||
$collection = $emoji->createCollectionFromSmilies(Smilies::getList())->getArrayCopy(true);
|
||||
foreach ($collection as $item) {
|
||||
$this->assertTrue(preg_match('(/images/.*)', $item['url']) === 1, $item['url']);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2023, the Friendica project
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Friendica\Test\src\Factory\Api\Mastodon;
|
||||
|
||||
use Friendica\Model\Post;
|
||||
use Friendica\DI;
|
||||
use Friendica\Test\FixtureTest;
|
||||
|
||||
class StatusTest extends FixtureTest
|
||||
{
|
||||
protected $status;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
DI::config()->set('system', 'no_smilies', false);
|
||||
$this->status = DI::mstdnStatus();
|
||||
}
|
||||
|
||||
public function testSimpleStatus()
|
||||
{
|
||||
$post = Post::selectFirst([], ['id' => 13]);
|
||||
$this->assertNotNull($post);
|
||||
$result = $this->status->createFromUriId($post['uri-id']);
|
||||
$this->assertNotNull($result);
|
||||
}
|
||||
|
||||
public function testSimpleEmojiStatus()
|
||||
{
|
||||
$post = Post::selectFirst([], ['id' => 14]);
|
||||
$this->assertNotNull($post);
|
||||
$result = $this->status->createFromUriId($post['uri-id'])->toArray();
|
||||
$this->assertEquals(':like: :friendica: no <code>:dislike</code> :p: :embarrassed: ❤', $result['content']);
|
||||
$emojis = array_fill_keys(['like', 'friendica', 'p', 'embarrassed'], true);
|
||||
$this->assertEquals(count($emojis), count($result['emojis']));
|
||||
foreach ($result['emojis'] as $emoji) {
|
||||
$this->assertTrue(array_key_exists($emoji['shortcode'], $emojis));
|
||||
$this->assertEquals(0, strpos($emoji['url'], 'http'));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2023, the Friendica project
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Friendica\Test\src\Protocol\ActivityPub;
|
||||
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Post;
|
||||
use Friendica\Protocol\ActivityPub\Transmitter;
|
||||
use Friendica\Test\FixtureTest;
|
||||
|
||||
class TransmitterTest extends FixtureTest
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
DI::config()->set('system', 'no_smilies', false);
|
||||
}
|
||||
|
||||
public function testEmojiPost()
|
||||
{
|
||||
$post = Post::selectFirst([], ['id' => 14]);
|
||||
$this->assertNotNull($post);
|
||||
$note = Transmitter::createNote($post);
|
||||
$this->assertNotNull($note);
|
||||
|
||||
$this->assertEquals(':like: :friendica: no <code>:dislike</code> :p: :embarrassed: ❤', $note['content']);
|
||||
$emojis = array_fill_keys(['like', 'friendica', 'p', 'embarrassed'], true);
|
||||
$this->assertEquals(count($emojis), count($note['tag']));
|
||||
foreach ($note['tag'] as $emoji) {
|
||||
$this->assertTrue(array_key_exists($emoji['name'], $emojis));
|
||||
$this->assertEquals('Emoji', $emoji['type']);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1413,6 +1413,9 @@ section #jotOpen {
|
|||
max-height: calc(100vh - 62px);
|
||||
}
|
||||
}
|
||||
#jot-modal #jot-modal-body {
|
||||
overflow: auto;
|
||||
}
|
||||
#jot-modal #jot-sections,
|
||||
#jot-modal #jot-modal-body,
|
||||
#jot-modal #profile-jot-form,
|
||||
|
@ -1423,7 +1426,6 @@ section #jotOpen {
|
|||
#jot-modal #item-Q0,
|
||||
#jot-modal #profile-jot-acl-wrapper,
|
||||
#jot-modal #acl-wrapper {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex: auto;
|
||||
flex-direction: column;
|
||||
|
|
Loading…
Reference in New Issue
Block a user