diff --git a/include/api.php b/include/api.php index e7706b44a..844b7e079 100644 --- a/include/api.php +++ b/include/api.php @@ -2126,6 +2126,8 @@ function api_format_items_activities($item, $type = "json") */ function api_format_item($item, $type = "json") { + return DI::twitterStatus()->createFromUriId($item['uri-id'], $item['uid']); + $author_user = DI::twitterUser()->createFromContactId($item['author-id'], $item['uid'])->toArray(); $owner_user = DI::twitterUser()->createFromContactId($item['owner-id'], $item['uid'])->toArray(); diff --git a/src/Factory/Api/Friendica/Activities.php b/src/Factory/Api/Friendica/Activities.php new file mode 100644 index 000000000..1979add35 --- /dev/null +++ b/src/Factory/Api/Friendica/Activities.php @@ -0,0 +1,98 @@ +. + * + */ + +namespace Friendica\Factory\Api\Friendica; + +use Friendica\App\BaseURL; +use Friendica\BaseFactory; +use Friendica\Database\DBA; +use Friendica\Model\Post; +use Friendica\Network\HTTPException; +use Friendica\Protocol\Activity; +use Psr\Log\LoggerInterface; +use Friendica\Factory\Api\Twitter\User as TwitterUser; + +class Activities extends BaseFactory +{ + /** @var BaseURL */ + private $baseUrl; + /** @var twitterUser entity */ + private $twitterUser; + + public function __construct(LoggerInterface $logger, BaseURL $baseURL, TwitterUser $twitteruser) + { + parent::__construct($logger); + + $this->twitterUser = $twitteruser; + $this->baseUrl = $baseURL; + } + + /** + * @param int $uriId Uri-ID of the item + * @return Array + * @throws HTTPException\InternalServerErrorException + */ + public function createFromUriId(int $uriId, int $uid): Array + { + $activities = [ + 'like' => [], + 'dislike' => [], + 'attendyes' => [], + 'attendno' => [], + 'attendmaybe' => [], + 'announce' => [], + ]; + + $condition = ['uid' => $uid, 'thr-parent-id' => $uriId, 'gravity' => GRAVITY_ACTIVITY]; + $ret = Post::selectForUser($uid, ['author-id', 'verb'], $condition); + + while ($parent_item = Post::fetch($ret)) { + // get user data and add it to the array of the activity + $user = $this->twitterUser->createFromContactId($parent_item['author-id'], $uid)->toArray(); + switch ($parent_item['verb']) { + case Activity::LIKE: + $activities['like'][] = $user; + break; + case Activity::DISLIKE: + $activities['dislike'][] = $user; + break; + case Activity::ATTEND: + $activities['attendyes'][] = $user; + break; + case Activity::ATTENDNO: + $activities['attendno'][] = $user; + break; + case Activity::ATTENDMAYBE: + $activities['attendmaybe'][] = $user; + break; + case Activity::ANNOUNCE: + $activities['announce'][] = $user; + break; + default: + break; + } + } + + DBA::close($ret); + + return $activities; + } +} diff --git a/src/Factory/Api/Twitter/Hashtag.php b/src/Factory/Api/Twitter/Hashtag.php new file mode 100644 index 000000000..80d1167b5 --- /dev/null +++ b/src/Factory/Api/Twitter/Hashtag.php @@ -0,0 +1,52 @@ +. + * + */ + +namespace Friendica\Factory\Api\Twitter; + +use Friendica\BaseFactory; +use Friendica\Network\HTTPException; +use Friendica\Model\Tag; +use Psr\Log\LoggerInterface; + +class Hashtag extends BaseFactory +{ + public function __construct(LoggerInterface $logger) + { + parent::__construct($logger); + } + + /** + * @param int $uriId Uri-ID of the attachments + * @return array + * @throws HTTPException\InternalServerErrorException + */ + public function createFromUriId(int $uriId, string $text): array + { + $hashtags = []; + foreach (Tag::getByURIId($uriId, [Tag::HASHTAG]) as $tag) { + $indices = []; + $object = new \Friendica\Object\Api\Twitter\Hashtag($tag['name'], $indices); + $hashtags[] = $object->toArray(); + } + + return $hashtags; + } +} diff --git a/src/Factory/Api/Twitter/Media.php b/src/Factory/Api/Twitter/Media.php new file mode 100644 index 000000000..301184b3e --- /dev/null +++ b/src/Factory/Api/Twitter/Media.php @@ -0,0 +1,67 @@ +. + * + */ + +namespace Friendica\Factory\Api\Twitter; + +use Friendica\App\BaseURL; +use Friendica\BaseFactory; +use Friendica\Network\HTTPException; +use Friendica\Model\Post; +use Psr\Log\LoggerInterface; + +class Media extends BaseFactory +{ + /** @var BaseURL */ + private $baseUrl; + + public function __construct(LoggerInterface $logger, BaseURL $baseURL) + { + parent::__construct($logger); + + $this->baseUrl = $baseURL; + } + + /** + * @param int $uriId Uri-ID of the attachments + * @return array + * @throws HTTPException\InternalServerErrorException + */ + public function createFromUriId(int $uriId, string $text): array + { + $attachments = []; + foreach (Post\Media::getByURIId($uriId, [Post\Media::AUDIO, Post\Media::IMAGE, Post\Media::VIDEO]) as $attachment) { + if ($attachment['type'] == Post\Media::IMAGE) { + $url = Post\Media::getUrlForId($attachment['id']); + } elseif (!empty($attachment['preview'])) { + $url = Post\Media::getPreviewUrlForId($attachment['id']); + } else { + $url = $attachment['url']; + } + + $indices = []; + + $object = new \Friendica\Object\Api\Twitter\Media($attachment, $url, $indices); + $attachments[] = $object->toArray(); + } + + return $attachments; + } +} diff --git a/src/Factory/Api/Twitter/Mention.php b/src/Factory/Api/Twitter/Mention.php new file mode 100644 index 000000000..a9d37d0c5 --- /dev/null +++ b/src/Factory/Api/Twitter/Mention.php @@ -0,0 +1,61 @@ +. + * + */ + +namespace Friendica\Factory\Api\Twitter; + +use Friendica\App\BaseURL; +use Friendica\BaseFactory; +use Friendica\Collection\Api\Mastodon\Mentions; +use Friendica\Model\Contact; +use Friendica\Model\Tag; +use Friendica\Network\HTTPException; +use Psr\Log\LoggerInterface; + +class Mention extends BaseFactory +{ + /** @var BaseURL */ + private $baseUrl; + + public function __construct(LoggerInterface $logger, BaseURL $baseURL) + { + parent::__construct($logger); + + $this->baseUrl = $baseURL; + } + + /** + * @param int $uriId Uri-ID of the item + * @return Array + * @throws HTTPException\InternalServerErrorException + */ + public function createFromUriId(int $uriId): Array + { + $mentions = []; + $tags = Tag::getByURIId($uriId, [Tag::MENTION, Tag::EXCLUSIVE_MENTION, Tag::IMPLICIT_MENTION]); + foreach ($tags as $tag) { + $indices = []; + $contact = Contact::getByURL($tag['url'], false); + $object = new \Friendica\Object\Api\Twitter\Mention($tag, $contact, $indices); + $mentions[] = $object->toArray(); + } + return $mentions; + } +} diff --git a/src/Factory/Api/Twitter/Status.php b/src/Factory/Api/Twitter/Status.php index b06b74e4d..35a75f772 100644 --- a/src/Factory/Api/Twitter/Status.php +++ b/src/Factory/Api/Twitter/Status.php @@ -23,8 +23,13 @@ namespace Friendica\Factory\Api\Twitter; use Friendica\BaseFactory; use Friendica\Content\Text\BBCode; +use Friendica\Content\Text\HTML; use Friendica\Database\Database; +use Friendica\Factory\Api\Friendica\Activities; use Friendica\Factory\Api\Twitter\User as TwitterUser; +use Friendica\Factory\Api\Twitter\Hashtag; +use Friendica\Factory\Api\Twitter\Mention; +use Friendica\Factory\Api\Twitter\Url; use Friendica\Model\Post; use Friendica\Model\Verb; use Friendica\Network\HTTPException; @@ -36,14 +41,29 @@ class Status extends BaseFactory { /** @var Database */ private $dba; - /** @var TwitterUser */ + /** @var twitterUser entity */ private $twitterUser; + /** @var Hashtag entity */ + private $hashtag; + /** @var Media entity */ + private $media; + /** @var Url entity */ + private $url; + /** @var Mention entity */ + private $mention; + /** @var Activities entity */ + private $activities; - public function __construct(LoggerInterface $logger, Database $dba, TwitterUser $twitteruser) + public function __construct(LoggerInterface $logger, Database $dba, TwitterUser $twitteruser, Hashtag $hashtag, Media $media, Url $url, Mention $mention, Activities $activities) { parent::__construct($logger); $this->dba = $dba; $this->twitterUser = $twitteruser; + $this->hashtag = $hashtag; + $this->media = $media; + $this->url = $url; + $this->mention = $mention; + $this->activities = $activities; } /** @@ -57,7 +77,7 @@ class Status extends BaseFactory public function createFromUriId(int $uriId, $uid = 0): \Friendica\Object\Api\Twitter\Status { $fields = ['id', 'parent', 'uri-id', 'uid', 'author-id', 'author-link', 'author-network', 'owner-id', 'starred', 'app', 'title', 'body', 'raw-body', 'created', 'network', - 'thr-parent-id', 'parent-author-id', 'parent-author-nick', 'language', 'uri', 'plink', 'private', 'vid', 'gravity']; + 'thr-parent-id', 'parent-author-id', 'parent-author-nick', 'language', 'uri', 'plink', 'private', 'vid', 'gravity', 'coord']; $item = Post::selectFirst($fields, ['uri-id' => $uriId, 'uid' => [0, $uid]], ['order' => ['uid' => true]]); if (!$item) { throw new HTTPException\NotFoundException('Item with URI ID ' . $uriId . ' not found' . ($uid ? ' for user ' . $uid : '.')); @@ -68,26 +88,38 @@ class Status extends BaseFactory $friendica_comments = Post::countPosts(['thr-parent-id' => $item['uri-id'], 'deleted' => false, 'gravity' => GRAVITY_COMMENT]); + $text = trim(HTML::toPlaintext(BBCode::convertForUriId($item['uri-id'], $item['body'], BBCode::API), 0)); + $geo = []; - //$mentions = $this->mstdnMentionFactory->createFromUriId($uriId)->getArrayCopy(); - //$tags = $this->mstdnTagFactory->createFromUriId($uriId); - //$attachments = $this->mstdnAttachementFactory->createFromUriId($uriId); - $entities = []; - $attachments = []; - $friendica_activities = []; + if ($item['coord'] != '') { + $coords = explode(' ', $item["coord"]); + if (count($coords) == 2) { + $geo = [ + 'type' => 'Point', + 'coordinates' => [(float) $coords[0], (float) $coords[1]] + ]; + } + } + + $hashtags = $this->hashtag->createFromUriId($uriId, $text); + $medias = $this->media->createFromUriId($uriId, $text); + $urls = $this->url->createFromUriId($uriId, $text); + $mentions = $this->mention->createFromUriId($uriId, $text); + + $friendica_activities = $this->activities->createFromUriId($uriId, $uid); $shared = BBCode::fetchShareAttributes($item['body']); if (!empty($shared['guid'])) { - //$shared_item = Post::selectFirst(['uri-id', 'plink'], ['guid' => $shared['guid']]); + $shared_item = Post::selectFirst(['uri-id', 'plink'], ['guid' => $shared['guid']]); - //$shared_uri_id = $shared_item['uri-id'] ?? 0; + $shared_uri_id = $shared_item['uri-id'] ?? 0; + + $hashtags = array_merge($hashtags, $this->hashtag->createFromUriId($shared_uri_id, $text)); + $medias = array_merge($medias, $this->media->createFromUriId($shared_uri_id, $text)); + $urls = array_merge($urls, $this->url->createFromUriId($shared_uri_id, $text)); + $mentions = array_merge($mentions, $this->mention->createFromUriId($shared_uri_id, $text)); - //$mentions = array_merge($mentions, $this->mstdnMentionFactory->createFromUriId($shared_uri_id)->getArrayCopy()); - //$tags = array_merge($tags, $this->mstdnTagFactory->createFromUriId($shared_uri_id)); - //$attachments = array_merge($attachments, $this->mstdnAttachementFactory->createFromUriId($shared_uri_id)); - $entities = []; - $attachments = []; $friendica_activities = []; } @@ -101,8 +133,10 @@ class Status extends BaseFactory $retweeted = []; } - $quoted = []; + $quoted = []; // @todo - return new \Friendica\Object\Api\Twitter\Status($item, $author, $owner, $retweeted, $quoted, $attachments, $geo, $friendica_activities, $entities, $friendica_comments); + $entities = ['hashtags' => $hashtags, 'media' => $medias, 'urls' => $urls, 'user_mentions' => $mentions]; + + return new \Friendica\Object\Api\Twitter\Status($text, $item, $author, $owner, $retweeted, $quoted, $geo, $friendica_activities, $entities, $friendica_comments); } } diff --git a/src/Factory/Api/Twitter/Url.php b/src/Factory/Api/Twitter/Url.php new file mode 100644 index 000000000..0f5080dbb --- /dev/null +++ b/src/Factory/Api/Twitter/Url.php @@ -0,0 +1,52 @@ +. + * + */ + +namespace Friendica\Factory\Api\Twitter; + +use Friendica\BaseFactory; +use Friendica\Network\HTTPException; +use Friendica\Model\Post; +use Psr\Log\LoggerInterface; + +class Url extends BaseFactory +{ + public function __construct(LoggerInterface $logger) + { + parent::__construct($logger); + } + + /** + * @param int $uriId Uri-ID of the attachments + * @return array + * @throws HTTPException\InternalServerErrorException + */ + public function createFromUriId(int $uriId): array + { + $attachments = []; + foreach (Post\Media::getByURIId($uriId, [Post\Media::HTML, Post\Media::PLAIN, Post\Media::TEXT]) as $attachment) { + $indices = []; + $object = new \Friendica\Object\Api\Twitter\Url($attachment, $indices); + $attachments[] = $object->toArray(); + } + + return $attachments; + } +} diff --git a/src/Factory/Api/Twitter/User.php b/src/Factory/Api/Twitter/User.php index e545bd78c..eae8c8091 100644 --- a/src/Factory/Api/Twitter/User.php +++ b/src/Factory/Api/Twitter/User.php @@ -50,7 +50,9 @@ class User extends BaseFactory $apcontact = APContact::getByURL($publicContact['url'], false); - return new \Friendica\Object\Api\Twitter\User($publicContact, $apcontact, $userContact, $skip_status, $include_user_entities); + $status = null; // @todo fetch last status + + return new \Friendica\Object\Api\Twitter\User($publicContact, $apcontact, $userContact, $status, $include_user_entities); } public function createFromUserId(int $uid, $skip_status = false, $include_user_entities = true) diff --git a/src/Object/Api/Twitter/Hashtag.php b/src/Object/Api/Twitter/Hashtag.php new file mode 100644 index 000000000..f6b7a491a --- /dev/null +++ b/src/Object/Api/Twitter/Hashtag.php @@ -0,0 +1,65 @@ +. + * + */ + +namespace Friendica\Object\Api\Twitter; + +use Friendica\BaseDataTransferObject; + +/** + * Class Hashtag + * + * @see https://developer.twitter.com/en/docs/twitter-api/v1/data-dictionary/object-model/entities#hashtags + */ +class Hashtag extends BaseDataTransferObject +{ + /** @var array */ + protected $indices; + /** @var string */ + protected $text; + + /** + * Creates a hashtag + * + * @param array $attachment + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + */ + public function __construct(string $name, array $indices) + { + $this->indices = $indices; + $this->text = $name; + } + + /** + * Returns the current entity as an array + * + * @return array + */ + public function toArray(): array + { + $status = parent::toArray(); + + if (empty($status['indices'])) { + unset($status['indices']); + } + + return $status; + } +} diff --git a/src/Object/Api/Twitter/Media.php b/src/Object/Api/Twitter/Media.php new file mode 100644 index 000000000..6256d427c --- /dev/null +++ b/src/Object/Api/Twitter/Media.php @@ -0,0 +1,108 @@ +. + * + */ + +namespace Friendica\Object\Api\Twitter; + +use Friendica\BaseDataTransferObject; +use Friendica\Model\Post; + +/** + * Class Media + * + * @see https://developer.twitter.com/en/docs/twitter-api/v1/data-dictionary/object-model/entities#media + */ +class Media extends BaseDataTransferObject +{ + /** @var string */ + protected $display_url; + /** @var string */ + protected $expanded_url; + /** @var int */ + protected $id; + /** @var string */ + protected $id_str; + /** @var array */ + protected $indices; + /** @var string */ + protected $media_url; + /** @var string */ + protected $media_url_https; + /** @var string */ + protected $sizes; + /** @var string */ + protected $type; + /** @var string */ + protected $url; + + /** + * Creates a media entity array + * + * @param array $attachment + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + */ + public function __construct(array $media, string $url, array $indices) + { + $this->display_url = $media['url']; + $this->expanded_url = $media['url']; + $this->id = $media['id']; + $this->id_str = (string)$media['id']; + $this->indices = $indices; + $this->media_url = $media['url']; + $this->media_url_https = $media['url']; + $this->type = $media['type'] == Post\Media::IMAGE ? 'photo' : 'video'; + $this->url = $url; + + if (!empty($media['height']) && !empty($media['width'])) { + if (($media['height'] <= 680) && ($media['width'] <= 680)) { + $size = 'small'; + } elseif (($media['height'] <= 1200) && ($media['width'] <= 1200)) { + $size = 'medium'; + } else { + $size = 'large'; + } + + $this->sizes = [ + $size => [ + 'h' => $media['height'], + 'resize' => 'fit', + 'w' => $media['width'], + ] + ]; + } + + } + + /** + * Returns the current entity as an array + * + * @return array + */ + public function toArray(): array + { + $status = parent::toArray(); + + if (empty($status['indices'])) { + unset($status['indices']); + } + + return $status; + } +} diff --git a/src/Object/Api/Twitter/Mention.php b/src/Object/Api/Twitter/Mention.php new file mode 100644 index 000000000..1c40f89de --- /dev/null +++ b/src/Object/Api/Twitter/Mention.php @@ -0,0 +1,77 @@ +. + * + */ + +namespace Friendica\Object\Api\Twitter; + +use Friendica\App\BaseURL; +use Friendica\BaseDataTransferObject; + +/** + * Class Mention + * + * @see https://developer.twitter.com/en/docs/twitter-api/v1/data-dictionary/object-model/entities#mentions + */ +class Mention extends BaseDataTransferObject +{ + /** @var int */ + protected $id; + /** @var string */ + protected $id_str; + /** @var array */ + protected $indices; + /** @var string */ + protected $name; + /** @var string */ + protected $screen_name; + + /** + * Creates a mention record from an tag-view record. + * + * @param BaseURL $baseUrl + * @param array $tag tag-view record + * @param array $contact contact table record + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + */ + public function __construct(array $tag, array $contact, array $indices) + { + $this->id = (string)($contact['id'] ?? 0); + $this->id_str = (string)($contact['id'] ?? 0); + $this->indices = $indices; + $this->name = $tag['name']; + $this->screen_name = $contact['nick']; + } + + /** + * Returns the current entity as an array + * + * @return array + */ + public function toArray(): array + { + $status = parent::toArray(); + + if (empty($status['indices'])) { + unset($status['indices']); + } + + return $status; + } +} diff --git a/src/Object/Api/Twitter/Status.php b/src/Object/Api/Twitter/Status.php index ac4d467d5..2bfdb055d 100644 --- a/src/Object/Api/Twitter/Status.php +++ b/src/Object/Api/Twitter/Status.php @@ -24,14 +24,13 @@ namespace Friendica\Object\Api\Twitter; use Friendica\BaseDataTransferObject; use Friendica\Content\ContactSelector; use Friendica\Content\Text\BBCode; -use Friendica\Content\Text\HTML; use Friendica\Model\Item; use Friendica\Util\DateTimeFormat; /** * Class Status * - * @see https://docs.joinmastodon.org/entities/status + * @see https://developer.twitter.com/en/docs/twitter-api/v1/data-dictionary/object-model/tweet */ class Status extends BaseDataTransferObject { @@ -83,11 +82,13 @@ class Status extends BaseDataTransferObject protected $statusnet_conversation_id; /** @var bool */ protected $friendica_private; - /** @var Attachment */ - protected $attachments = []; protected $geo; + /** @var array */ protected $friendica_activities; + /** @var array */ protected $entities; + /** @var array */ + protected $extended_entities; /** * Creates a status record from an item record. @@ -95,7 +96,7 @@ class Status extends BaseDataTransferObject * @param array $item * @throws \Friendica\Network\HTTPException\InternalServerErrorException */ - public function __construct(array $item, User $author, User $owner, array $retweeted, array $quoted, array $attachments, array $geo, array $friendica_activities, array $entities, int $friendica_comments) + public function __construct(string $text, array $item, User $author, User $owner, array $retweeted, array $quoted, array $geo, array $friendica_activities, array $entities, int $friendica_comments) { $this->id = (int)$item['id']; $this->id_str = (string)$item['id']; @@ -111,7 +112,7 @@ class Status extends BaseDataTransferObject $this->in_reply_to_screen_name = $item['parent-author-nick']; } - $this->text = trim(HTML::toPlaintext(BBCode::convertForUriId($item['uri-id'], $item['body'], BBCode::API), 0)); + $this->text = $text; $this->friendica_title = $item['title']; $this->statusnet_html = BBCode::convertForUriId($item['uri-id'], BBCode::setMentionsToNicknames($item['raw-body'] ?? $item['body']), BBCode::API); $this->friendica_html = BBCode::convertForUriId($item['uri-id'], $item['body'], BBCode::EXTERNAL); @@ -126,10 +127,10 @@ class Status extends BaseDataTransferObject $this->favorited = (bool)$item['starred']; $this->friendica_comments = $friendica_comments; $this->source = $item['app'] ?: 'web'; - $this->attachments = $attachments; $this->geo = $geo; $this->friendica_activities = $friendica_activities; $this->entities = $entities; + $this->extended_entities = $entities; if ($this->source == 'web') { $this->source = ContactSelector::networkToName($item['author-network'], $item['author-link'], $item['network']); diff --git a/src/Object/Api/Twitter/Url.php b/src/Object/Api/Twitter/Url.php new file mode 100644 index 000000000..b42c2269e --- /dev/null +++ b/src/Object/Api/Twitter/Url.php @@ -0,0 +1,71 @@ +. + * + */ + +namespace Friendica\Object\Api\Twitter; + +use Friendica\BaseDataTransferObject; + +/** + * Class Url + * + * @see https://developer.twitter.com/en/docs/twitter-api/v1/data-dictionary/object-model/entities#urls + */ +class Url extends BaseDataTransferObject +{ + /** @var string */ + protected $display_url; + /** @var string */ + protected $expanded_url; + /** @var array */ + protected $indices; + /** @var string */ + protected $url; + + /** + * Creates an URL entity array + * + * @param array $attachment + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + */ + public function __construct(array $media, array $indices) + { + $this->display_url = $media['url']; + $this->expanded_url = $media['url']; + $this->indices = $indices; + $this->url = $media['url']; + } + + /** + * Returns the current entity as an array + * + * @return array + */ + public function toArray(): array + { + $status = parent::toArray(); + + if (empty($status['indices'])) { + unset($status['indices']); + } + + return $status; + } +} diff --git a/src/Object/Api/Twitter/User.php b/src/Object/Api/Twitter/User.php index bbc4efc0a..b67426326 100644 --- a/src/Object/Api/Twitter/User.php +++ b/src/Object/Api/Twitter/User.php @@ -99,7 +99,7 @@ class User extends BaseDataTransferObject * @param bool $include_user_entities Whether to add the entities property * @throws \Friendica\Network\HTTPException\InternalServerErrorException */ - public function __construct(array $publicContact, array $apcontact = [], array $userContact = [], $skip_status = false, $include_user_entities = true) + public function __construct(array $publicContact, array $apcontact = [], array $userContact = [], $status = null, $include_user_entities = true) { $uid = $userContact['uid'] ?? 0; @@ -133,8 +133,11 @@ class User extends BaseDataTransferObject $this->default_profile = false; $this->default_profile_image = false; - // @TODO Replace skip_status parameter with an optional Status parameter - unset($this->status); + if (!empty($status)) { + $this->status = $status; + } else { + unset($this->status); + } // Unused optional fields unset($this->withheld_in_countries);