. * * @category Plugin * @package GNUsocial * @author Daniel Supernault * @author Diogo Cordeiro * @copyright 2018 Free Software Foundation http://fsf.org * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link https://www.gnu.org/software/social/ */ if (!defined('GNUSOCIAL')) { exit(1); } /** * ActivityPub notice representation * * @category Plugin * @package GNUsocial * @author Diogo Cordeiro * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://www.gnu.org/software/social/ */ class Activitypub_notice extends Managed_DataObject { /** * Generates a pretty notice from a Notice object * * @author Diogo Cordeiro * @param Notice $notice * @return pretty array to be used in a response */ public static function notice_to_array($notice) { $profile = $notice->getProfile(); $attachments = []; foreach ($notice->attachments() as $attachment) { $attachments[] = Activitypub_attachment::attachment_to_array($attachment); } $tags = []; foreach ($notice->getTags() as $tag) { if ($tag != "") { // Hacky workaround to avoid stupid outputs $tags[] = Activitypub_tag::tag_to_array($tag); } } $cc = [common_local_url('apActorFollowers', ['id' => $profile->getID()])]; foreach ($notice->getAttentionProfiles() as $to_profile) { $cc[] = $href = $to_profile->getUri(); $tags[] = Activitypub_mention_tag::mention_tag_to_array_from_values($href, $to_profile->getNickname().'@'.parse_url($href, PHP_URL_HOST)); } // In a world without walls and fences, we should make everything Public! $to[]= 'https://www.w3.org/ns/activitystreams#Public'; $item = [ '@context' => [ 'https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1' ], 'id' => $notice->getUrl(), 'type' => 'Note', 'published' => str_replace(' ', 'T', $notice->getCreated()).'Z', 'url' => $notice->getUrl(), 'attributedTo' => ActivityPubPlugin::actor_uri($profile), 'to' => ['https://www.w3.org/ns/activitystreams#Public'], 'cc' => $cc, 'atomUri' => $notice->getUrl(), 'conversation' => $notice->getConversationUrl(), 'content' => $notice->getRendered(), 'isLocal' => $notice->isLocal(), 'attachment' => $attachments, 'tag' => $tags ]; // Is this a reply? if (!empty($notice->reply_to)) { $item['inReplyTo'] = Notice::getById($notice->reply_to)->getUrl(); $item['inReplyToAtomUri'] = Notice::getById($notice->reply_to)->getUrl(); } // Do we have a location for this notice? try { $location = Notice_location::locFromStored($notice); $item['latitude'] = $location->lat; $item['longitude'] = $location->lon; } catch (Exception $e) { // Apparently no. } return $item; } /** * Create a Notice via ActivityPub Note Object. * Returns created Notice. * * @author Diogo Cordeiro * @param Array $object * @param Profile|null $actor_profile * @return Notice * @throws Exception */ public static function create_notice($object, $actor_profile = null) { $id = $object['id']; // int32 $url = $object['url']; // string $content = $object['content']; // string $cc = $object['cc']; // array|string // possible keys: ['inReplyTo', 'latitude', 'longitude', 'attachment'] $settings = []; if (isset($object['inReplyTo'])) { $settings['inReplyTo'] = $object['inReplyTo']; } if (isset($object['latitude'])) { $settings['latitude'] = $object['latitude']; } if (isset($object['longitude'])) { $settings['longitude'] = $object['longitude']; } if (isset($object['attachment'])) { $settings['attachment'] = $object['attachment']; } // Ensure Actor Profile if (is_null($actor_profile)) { $actor_profile = ActivityPub_explorer::get_profile_from_url($object['actor']); } $act = new Activity(); $act->verb = ActivityVerb::POST; $act->time = time(); $act->actor = $actor_profile->asActivityObject(); $act->context = new ActivityContext(); $options = ['source' => 'ActivityPub', 'uri' => $id, 'url' => $url]; // Do we have an attachment? if (isset($settings['attachment'][0])) { $attach = $settings['attachment'][0]; $attach_url = $settings['attachment'][0]['url']; // Is it an image? if (ActivityPubPlugin::$store_images_from_remote_notes_attachments && substr($attach["mediaType"], 0, 5) == "image") { $temp_filename = tempnam(sys_get_temp_dir(), 'apCreateNoteAttach_'); try { $imgData = HTTPClient::quickGet($attach_url); // Make sure it's at least an image file. ImageFile can do the rest. if (false === getimagesizefromstring($imgData)) { common_debug('ActivityPub Create Notice: Failed because the downloaded image: '.$attach_url. 'is not valid.'); throw new UnsupportedMediaException('Downloaded image was not an image.'); } file_put_contents($temp_filename, $imgData); common_debug('ActivityPub Create Notice: Stored dowloaded image in: '.$temp_filename); $id = $actor_profile->getID(); $imagefile = new ImageFile(null, $temp_filename); $filename = hash(File::FILEHASH_ALG, $imgData).image_type_to_extension($imagefile->type); unset($imgData); // No need to carry this in memory. rename($temp_filename, File::path($filename)); common_debug('ActivityPub Create Notice: Moved image from: '.$temp_filename.' to '.$filename); $mediaFile = new MediaFile($filename, $attach['mediaType']); $act->enclosures[] = $mediaFile->getEnclosure(); } catch (Exception $e) { common_debug('ActivityPub Create Notice: Something went wrong while processing the image from: '.$attach_url.' details: '.$e->getMessage()); unlink($temp_filename); } } $content .= ($content==='' ? '' : ' ') . '
Remote Attachment Source'; } // Is this a reply? if (isset($settings['inReplyTo'])) { try { $inReplyTo = ActivityPubPlugin::grab_notice_from_url($settings['inReplyTo']); } catch (Exception $e) { throw new Exception('Invalid Object inReplyTo value: '.$e->getMessage()); } $act->context->replyToID = $inReplyTo->getUri(); $act->context->replyToUrl = $inReplyTo->getUrl(); } else { $inReplyTo = null; } $discovery = new Activitypub_explorer; // Generate Cc objects $cc_profiles = []; if (is_array($cc)) { // Remove duplicates from Cc actors set array_unique($cc); foreach ($cc as $cc_url) { try { $cc_profiles = array_merge($cc_profiles, $discovery->lookup($cc_url)); } catch (Exception $e) { // Invalid actor found, just let it go. // TODO: Fallback to OStatus } } } elseif (empty($cc) || in_array($cc, ACTIVITYPUB_PUBLIC_TO)) { // No need to do anything else at this point, let's just break out the if } else { try { $cc_profiles = $discovery->lookup($cc); } catch (Exception $e) { // Invalid actor found, just let it go. // TODO: Fallback to OStatus } } unset($discovery); foreach ($cc_profiles as $cp) { $act->context->attention[ActivityPubPlugin::actor_uri($cp)] = 'http://activitystrea.ms/schema/1.0/person'; } // Add location if that is set if (isset($settings['latitude'], $settings['longitude'])) { $act->context->location = Location::fromLatLon($settings['latitude'], $settings['longitude']); } // Reject notice if it is too long (without the HTML) if (Notice::contentTooLong($content)) { //throw new Exception('That\'s too long. Maximum notice size is %d character.'); } $actobj = new ActivityObject(); $actobj->type = ActivityObject::NOTE; $actobj->content = strip_tags($content, '