Now the code is following most of PSR Various changes from various branches (some testing will be required) Fixed various issues
This commit is contained in:
parent
e377b87ff7
commit
eaad9423dd
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/vendor/
|
@ -19,8 +19,8 @@
|
||||
*
|
||||
* @category Plugin
|
||||
* @package GNUsocial
|
||||
* @author Daniel Supernault <danielsupernault@gmail.com>
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @author Daniel Supernault <danielsupernault@gmail.com>
|
||||
* @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/
|
||||
@ -37,23 +37,58 @@ require_once __DIR__ . DIRECTORY_SEPARATOR . "utils" . DIRECTORY_SEPARATOR . "po
|
||||
/**
|
||||
* @category Plugin
|
||||
* @package GNUsocial
|
||||
* @author Daniel Supernault <danielsupernault@gmail.com>
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @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 ActivityPubPlugin extends Plugin
|
||||
{
|
||||
/**
|
||||
* Returns a Actor's URI from its local $profile
|
||||
* Works both for local and remote users.
|
||||
*
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @param Profile $profile Actor's local profile
|
||||
* @return string Actor's URI
|
||||
*/
|
||||
public static function actor_uri($profile)
|
||||
{
|
||||
if ($profile->isLocal()) {
|
||||
return common_root_url()."index.php/user/".$profile->getID();
|
||||
} else {
|
||||
return $profile->getUri();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Actor's URL from its local $profile
|
||||
* Works both for local and remote users.
|
||||
*
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @param Profile $profile Actor's local profile
|
||||
* @return string Actor's URL
|
||||
*/
|
||||
public static function actor_url($profile)
|
||||
{
|
||||
return actor_uri($profile)."/";
|
||||
}
|
||||
|
||||
public static function stripUrlPath($url)
|
||||
{
|
||||
$urlParts = parse_url($url);
|
||||
$newUrl = $urlParts['scheme'] . "://" . $urlParts['host'] . "/";
|
||||
return $newUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get remote user's ActivityPub_profile via a identifier
|
||||
* (https://www.w3.org/TR/activitypub/#obj-id)
|
||||
*
|
||||
* @author GNU Social
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @param string $arg A remote user identifier
|
||||
* @return Activitypub_profile|null Valid profile in success | null otherwise
|
||||
*/
|
||||
protected function pull_remote_profile ($arg)
|
||||
public static function pull_remote_profile($arg)
|
||||
{
|
||||
if (preg_match('!^((?:\w+\.)*\w+@(?:\w+\.)*\w+(?:\w+\-\w+)*\.\w+)$!', $arg)) {
|
||||
// webfinger lookup
|
||||
@ -96,29 +131,62 @@ class ActivityPubPlugin extends Plugin
|
||||
*/
|
||||
public function onRouterInitialized(URLMapper $m)
|
||||
{
|
||||
ActivityPubURLMapperOverwrite::overwrite_variable ($m, ':nickname',
|
||||
ActivityPubURLMapperOverwrite::overwrite_variable(
|
||||
$m,
|
||||
'user/:id',
|
||||
['action' => 'showstream'],
|
||||
['id' => '[0-9]+'],
|
||||
'apActorProfile'
|
||||
);
|
||||
|
||||
// Special route for webfinger purposes
|
||||
ActivityPubURLMapperOverwrite::overwrite_variable(
|
||||
$m,
|
||||
':nickname',
|
||||
['action' => 'showstream'],
|
||||
['nickname' => Nickname::DISPLAY_FMT],
|
||||
'apActorProfile');
|
||||
'apActorProfile'
|
||||
);
|
||||
|
||||
$m->connect (':nickname/liked.json',
|
||||
['action' => 'apActorLikedCollection'],
|
||||
['nickname' => Nickname::DISPLAY_FMT]);
|
||||
$m->connect(
|
||||
':nickname/remote_follow',
|
||||
['action' => 'apRemoteFollow'],
|
||||
['nickname' => '[A-Za-z0-9_-]+']
|
||||
);
|
||||
|
||||
$m->connect (':nickname/followers.json',
|
||||
$m->connect(
|
||||
'activitypub/authorize_follow',
|
||||
['action' => 'apAuthorizeRemoteFollow']
|
||||
);
|
||||
|
||||
$m->connect(
|
||||
'user/:id/liked.json',
|
||||
['action' => 'apActorLiked'],
|
||||
['id' => '[0-9]+']
|
||||
);
|
||||
|
||||
$m->connect(
|
||||
'user/:id/followers.json',
|
||||
['action' => 'apActorFollowers'],
|
||||
['nickname' => Nickname::DISPLAY_FMT]);
|
||||
['id' => '[0-9]+']
|
||||
);
|
||||
|
||||
$m->connect (':nickname/following.json',
|
||||
$m->connect(
|
||||
'user/:id/following.json',
|
||||
['action' => 'apActorFollowing'],
|
||||
['nickname' => Nickname::DISPLAY_FMT]);
|
||||
['id' => '[0-9]+']
|
||||
);
|
||||
|
||||
$m->connect (':nickname/inbox.json',
|
||||
$m->connect(
|
||||
'user/:id/inbox.json',
|
||||
['action' => 'apActorInbox'],
|
||||
['nickname' => Nickname::DISPLAY_FMT]);
|
||||
['id' => '[0-9]+']
|
||||
);
|
||||
|
||||
$m->connect ('inbox.json',
|
||||
array('action' => 'apSharedInbox'));
|
||||
$m->connect(
|
||||
'inbox.json',
|
||||
['action' => 'apSharedInbox']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -131,7 +199,7 @@ class ActivityPubPlugin extends Plugin
|
||||
{
|
||||
$versions[] = [ 'name' => 'ActivityPub',
|
||||
'version' => GNUSOCIAL_VERSION,
|
||||
'author' => 'Daniel Supernault, Diogo Cordeiro',
|
||||
'author' => 'Diogo Cordeiro, Daniel Supernault',
|
||||
'homepage' => 'https://www.gnu.org/software/social/',
|
||||
'rawdescription' =>
|
||||
// Todo: Translation
|
||||
@ -145,13 +213,135 @@ class ActivityPubPlugin extends Plugin
|
||||
*
|
||||
* @return boolean hook true
|
||||
*/
|
||||
function onCheckSchema ()
|
||||
public function onCheckSchema()
|
||||
{
|
||||
$schema = Schema::get();
|
||||
$schema->ensureTable('Activitypub_profile', Activitypub_profile::schemaDef());
|
||||
$schema->ensureTable('Activitypub_pending_follow_requests', Activitypub_pending_follow_requests::schemaDef());
|
||||
return true;
|
||||
}
|
||||
|
||||
/********************************************************
|
||||
* Remote Subscription Events *
|
||||
********************************************************/
|
||||
|
||||
/**
|
||||
* Add in an ActivityPub subscribe button
|
||||
*
|
||||
* @author GNU Social
|
||||
* @param type $output
|
||||
* @param type $profile
|
||||
* @return boolean hook false
|
||||
*/
|
||||
public function onStartProfileRemoteSubscribe($output, $profile)
|
||||
{
|
||||
$this->onStartProfileListItemActionElements($output, $profile);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add in an ActivityPub subscribe button
|
||||
*
|
||||
* @author GNU Social
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @param Action|Widget $item
|
||||
* @return boolean hook return value
|
||||
* @throws ServerException
|
||||
*/
|
||||
public function onStartProfileListItemActionElements($item)
|
||||
{ // FIXME: This one can accept both an Action and a Widget. Confusing! Refactor to (HTMLOutputter $out, Profile $target)!
|
||||
if (common_logged_in()) {
|
||||
// only non-logged in users get to see the "remote subscribe" form
|
||||
return true;
|
||||
} elseif (!$item->getTarget()->isLocal()) {
|
||||
// we can (for now) only provide remote subscribe forms for local users
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($item instanceof ProfileAction) {
|
||||
$output = $item;
|
||||
} elseif ($item instanceof Widget) {
|
||||
$output = $item->out;
|
||||
} else {
|
||||
// Bad $item class, don't know how to use this for outputting!
|
||||
throw new ServerException('Bad item type for onStartProfileListItemActionElements');
|
||||
}
|
||||
|
||||
// Add an ActivityPub subscribe
|
||||
$output->elementStart('li', 'entity_subscribe');
|
||||
$url = common_local_url(
|
||||
'apRemoteFollow',
|
||||
array('nickname' => $item->getTarget()->getNickname())
|
||||
);
|
||||
$output->element(
|
||||
'a',
|
||||
array('href' => $url,
|
||||
'class' => 'entity_remote_subscribe'),
|
||||
// TRANS: Link text for a user to subscribe to an OStatus user.
|
||||
_m('Subscribe')
|
||||
);
|
||||
$output->elementEnd('li');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add in an ActivityPub subscribe button
|
||||
*
|
||||
* @author GNU Social
|
||||
* @param type $action
|
||||
* @param string $target
|
||||
* @return boolean hook return value
|
||||
*/
|
||||
public function showEntityRemoteSubscribe($action, $target='apRemoteFollow')
|
||||
{
|
||||
if (!$action->getScoped() instanceof Profile) {
|
||||
// early return if we're not logged in
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($action->getScoped()->sameAs($action->getTarget())) {
|
||||
$action->elementStart('div', 'entity_actions');
|
||||
$action->elementStart('p', array('id' => 'entity_remote_subscribe',
|
||||
'class' => 'entity_subscribe'));
|
||||
$action->element(
|
||||
'a',
|
||||
array('href' => common_local_url($target),
|
||||
'class' => 'entity_remote_subscribe'),
|
||||
// TRANS: Link text for link to remote subscribe.
|
||||
_m('Remote')
|
||||
);
|
||||
$action->elementEnd('p');
|
||||
$action->elementEnd('div');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dummy string on AccountProfileBlock stating that ActivityPub is active
|
||||
* this is more of a placeholder for eventual useful stuff ._.
|
||||
*
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @return boolean hook return value
|
||||
*/
|
||||
public function onEndShowAccountProfileBlock(HTMLOutputter $out, Profile $profile)
|
||||
{
|
||||
if ($profile->isLocal()) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
$aprofile = Activitypub_profile::getKV('profile_id', $profile->id);
|
||||
} catch (NoResultException $e) {
|
||||
// Not a remote ActivityPub_profile! Maybe some other network
|
||||
// that has imported a non-local user (e.g.: OStatus)?
|
||||
return true;
|
||||
}
|
||||
|
||||
$out->elementStart('dl', 'entity_tags activitypub_profile');
|
||||
$out->element('dt', null, _m('ActivityPub'));
|
||||
$out->element('dd', null, _m('Active'));
|
||||
$out->elementEnd('dl');
|
||||
}
|
||||
|
||||
/********************************************************
|
||||
* Discovery Events *
|
||||
********************************************************/
|
||||
@ -164,13 +354,15 @@ class ActivityPubPlugin extends Plugin
|
||||
* @param string $preMention Character(s) that signals a mention ('@', '!'...)
|
||||
* @return array The matching IDs (without $preMention) and each respective position in the given string.
|
||||
*/
|
||||
static function extractWebfingerIds ($text, $preMention='@')
|
||||
public static function extractWebfingerIds($text, $preMention='@')
|
||||
{
|
||||
$wmatches = array();
|
||||
$result = preg_match_all ('/(?<!\S)'.preg_quote ($preMention, '/').'('.Nickname::WEBFINGER_FMT.')/',
|
||||
$result = preg_match_all(
|
||||
'/(?<!\S)'.preg_quote($preMention, '/').'('.Nickname::WEBFINGER_FMT.')/',
|
||||
$text,
|
||||
$wmatches,
|
||||
PREG_OFFSET_CAPTURE);
|
||||
PREG_OFFSET_CAPTURE
|
||||
);
|
||||
if ($result === false) {
|
||||
common_log(LOG_ERR, __METHOD__ . ': Error parsing webfinger IDs from text (preg_last_error=='.preg_last_error().').');
|
||||
} elseif (count($wmatches)) {
|
||||
@ -187,15 +379,17 @@ class ActivityPubPlugin extends Plugin
|
||||
* @param string $preMention Character(s) that signals a mention ('@', '!'...)
|
||||
* @return array The matching URLs (without @ or acct:) and each respective position in the given string.
|
||||
*/
|
||||
static function extractUrlMentions ($text, $preMention='@')
|
||||
public static function extractUrlMentions($text, $preMention='@')
|
||||
{
|
||||
$wmatches = array();
|
||||
// In the regexp below we need to match / _before_ URL_REGEX_VALID_PATH_CHARS because it otherwise gets merged
|
||||
// with the TLD before (but / is in URL_REGEX_VALID_PATH_CHARS anyway, it's just its positioning that is important)
|
||||
$result = preg_match_all ('/(?:^|\s+)'.preg_quote ($preMention, '/').'('.URL_REGEX_DOMAIN_NAME.'(?:\/['.URL_REGEX_VALID_PATH_CHARS.']*)*)/',
|
||||
$result = preg_match_all(
|
||||
'/(?:^|\s+)'.preg_quote($preMention, '/').'('.URL_REGEX_DOMAIN_NAME.'(?:\/['.URL_REGEX_VALID_PATH_CHARS.']*)*)/',
|
||||
$text,
|
||||
$wmatches,
|
||||
PREG_OFFSET_CAPTURE);
|
||||
PREG_OFFSET_CAPTURE
|
||||
);
|
||||
if ($result === false) {
|
||||
common_log(LOG_ERR, __METHOD__ . ': Error parsing profile URL mentions from text (preg_last_error=='.preg_last_error().').');
|
||||
} elseif (count($wmatches)) {
|
||||
@ -216,7 +410,7 @@ class ActivityPubPlugin extends Plugin
|
||||
* @param array &$mention in/out param: set of found mentions
|
||||
* @return boolean hook return value
|
||||
*/
|
||||
function onEndFindMentions(Profile $sender, $text, &$mentions)
|
||||
public function onEndFindMentions(Profile $sender, $text, &$mentions)
|
||||
{
|
||||
$matches = array();
|
||||
|
||||
@ -300,7 +494,7 @@ class ActivityPubPlugin extends Plugin
|
||||
* @param Profile &$profile
|
||||
* @return hook return code
|
||||
*/
|
||||
function onStartCommandGetProfile ($command, $arg, &$profile)
|
||||
public function onStartCommandGetProfile($command, $arg, &$profile)
|
||||
{
|
||||
try {
|
||||
$aprofile = $this->pull_remote_profile($arg);
|
||||
@ -322,7 +516,7 @@ class ActivityPubPlugin extends Plugin
|
||||
* @param string $uri in/out
|
||||
* @return mixed hook return code
|
||||
*/
|
||||
function onStartGetProfileUri ($profile, &$uri)
|
||||
public function onStartGetProfileUri($profile, &$uri)
|
||||
{
|
||||
$aprofile = Activitypub_profile::getKV('profile_id', $profile->id);
|
||||
if ($aprofile instanceof Activitypub_profile) {
|
||||
@ -332,33 +526,6 @@ class ActivityPubPlugin extends Plugin
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Dummy string on AccountProfileBlock stating that ActivityPub is active
|
||||
* this is more of a placeholder for eventual useful stuff ._.
|
||||
*
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @return void
|
||||
*/
|
||||
function onEndShowAccountProfileBlock (HTMLOutputter $out, Profile $profile)
|
||||
{
|
||||
if ($profile->isLocal()) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
$aprofile = Activitypub_profile::getKV ('profile_id', $profile->id);
|
||||
} catch (NoResultException $e) {
|
||||
// Not a remote ActivityPub_profile! Maybe some other network
|
||||
// that has imported a non-local user (e.g.: OStatus)?
|
||||
return true;
|
||||
}
|
||||
|
||||
$out->elementStart('dl', 'entity_tags activitypub_profile');
|
||||
$out->element('dt', null, _m('ActivityPub'));
|
||||
$out->element('dd', null, _m('Active'));
|
||||
$out->elementEnd('dl');
|
||||
}
|
||||
|
||||
/**
|
||||
* Profile from URI.
|
||||
*
|
||||
@ -368,7 +535,7 @@ class ActivityPubPlugin extends Plugin
|
||||
* @param Profile &$profile in/out param: Profile got from URI
|
||||
* @return mixed hook return code
|
||||
*/
|
||||
function onStartGetProfileFromURI ($uri, &$profile)
|
||||
public function onStartGetProfileFromURI($uri, &$profile)
|
||||
{
|
||||
// Don't want to do Web-based discovery on our own server,
|
||||
// so we check locally first. This duplicates the functionality
|
||||
@ -411,7 +578,7 @@ class ActivityPubPlugin extends Plugin
|
||||
* @return hook return value
|
||||
* @throws Exception
|
||||
*/
|
||||
function onEndSubscribe (Profile $profile, Profile $other)
|
||||
public function onStartSubscribe(Profile $profile, Profile $other)
|
||||
{
|
||||
if (!$profile->isLocal() || $other->isLocal()) {
|
||||
return true;
|
||||
@ -438,7 +605,7 @@ class ActivityPubPlugin extends Plugin
|
||||
* @param Profile $other
|
||||
* @return hook return value
|
||||
*/
|
||||
function onEndUnsubscribe (Profile $profile, Profile $other)
|
||||
public function onStartUnsubscribe(Profile $profile, Profile $other)
|
||||
{
|
||||
if (!$profile->isLocal() || $other->isLocal()) {
|
||||
return true;
|
||||
@ -465,7 +632,7 @@ class ActivityPubPlugin extends Plugin
|
||||
* @param Notice $notice Notice being favored
|
||||
* @return hook return value
|
||||
*/
|
||||
function onEndFavorNotice (Profile $profile, Notice $notice)
|
||||
public function onEndFavorNotice(Profile $profile, Notice $notice)
|
||||
{
|
||||
// Only distribute local users' favor actions, remote users
|
||||
// will have already distributed theirs.
|
||||
@ -524,7 +691,7 @@ class ActivityPubPlugin extends Plugin
|
||||
* @param Notice $notice Notice being favored
|
||||
* @return hook return value
|
||||
*/
|
||||
function onEndDisfavorNotice (Profile $profile, Notice $notice)
|
||||
public function onEndDisfavorNotice(Profile $profile, Notice $notice)
|
||||
{
|
||||
// Only distribute local users' favor actions, remote users
|
||||
// will have already distributed theirs.
|
||||
@ -581,7 +748,7 @@ class ActivityPubPlugin extends Plugin
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @return boolean hook flag
|
||||
*/
|
||||
public function onEndDeleteOwnNotice ($user, $notice)
|
||||
public function onStartDeleteOwnNotice($user, $notice)
|
||||
{
|
||||
$profile = $user->getProfile();
|
||||
|
||||
@ -634,7 +801,7 @@ class ActivityPubPlugin extends Plugin
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @return boolean hook flag
|
||||
*/
|
||||
function onStartNoticeDistribute ($notice)
|
||||
public function onStartNoticeDistribute($notice)
|
||||
{
|
||||
assert($notice->id > 0); // Ignore if not a valid notice
|
||||
|
||||
@ -674,8 +841,10 @@ class ActivityPubPlugin extends Plugin
|
||||
|
||||
// Ignore for activity/non-post-verb notices
|
||||
if (method_exists('ActivityUtils', 'compareVerbs')) {
|
||||
$is_post_verb = ActivityUtils::compareVerbs ($notice->verb,
|
||||
array (ActivityVerb::POST));
|
||||
$is_post_verb = ActivityUtils::compareVerbs(
|
||||
$notice->verb,
|
||||
array(ActivityVerb::POST)
|
||||
);
|
||||
} else {
|
||||
$is_post_verb = ($notice->verb == ActivityVerb::POST ? true : false);
|
||||
}
|
||||
@ -724,7 +893,7 @@ class ActivityPubPlugin extends Plugin
|
||||
* @param string out &$title
|
||||
* @return mixed hook return code
|
||||
*/
|
||||
function onStartNoticeSourceLink ($notice, &$name, &$url, &$title)
|
||||
public function onStartNoticeSourceLink($notice, &$name, &$url, &$title)
|
||||
{
|
||||
// If we don't handle this, keep the event handler going
|
||||
if (!in_array($notice->source, array('ActivityPub', 'share'))) {
|
||||
@ -755,12 +924,50 @@ class ActivityPubPlugin extends Plugin
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin return handler
|
||||
*/
|
||||
class ActivityPubReturn
|
||||
{
|
||||
/**
|
||||
* Return a valid answer
|
||||
*
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @param array $res
|
||||
* @return void
|
||||
*/
|
||||
public static function answer($res)
|
||||
{
|
||||
header('Content-Type: application/activity+json');
|
||||
echo json_encode($res, JSON_UNESCAPED_SLASHES | (isset($_GET["pretty"]) ? JSON_PRETTY_PRINT : null));
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an error
|
||||
*
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @param string $m
|
||||
* @param int32 $code
|
||||
* @return void
|
||||
*/
|
||||
public static function error($m, $code = 500)
|
||||
{
|
||||
http_response_code($code);
|
||||
header('Content-Type: application/activity+json');
|
||||
$res[] = Activitypub_error::error_message_to_array($m);
|
||||
echo json_encode($res, JSON_UNESCAPED_SLASHES);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrites variables in URL-mapping
|
||||
*/
|
||||
class ActivityPubURLMapperOverwrite extends URLMapper
|
||||
{
|
||||
static function overwrite_variable ($m, $path, $args, $paramPatterns, $newaction) {
|
||||
public static function overwrite_variable($m, $path, $args, $paramPatterns, $newaction)
|
||||
{
|
||||
$mimes = [
|
||||
'application/activity+json',
|
||||
'application/ld+json',
|
||||
@ -780,40 +987,3 @@ class ActivityPubURLMapperOverwrite extends URLMapper
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin return handler
|
||||
*/
|
||||
class ActivityPubReturn
|
||||
{
|
||||
/**
|
||||
* Return a valid answer
|
||||
*
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @param array $res
|
||||
* @return void
|
||||
*/
|
||||
static function answer ($res)
|
||||
{
|
||||
header ('Content-Type: application/activity+json');
|
||||
echo json_encode ($res, JSON_UNESCAPED_SLASHES | (isset ($_GET["pretty"]) ? JSON_PRETTY_PRINT : null));
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an error
|
||||
*
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @param string $m
|
||||
* @param int32 $code
|
||||
* @return void
|
||||
*/
|
||||
static function error ($m, $code = 500)
|
||||
{
|
||||
http_response_code ($code);
|
||||
header ('Content-Type: application/activity+json');
|
||||
$res[] = Activitypub_error::error_message_to_array ($m);
|
||||
echo json_encode ($res, JSON_UNESCAPED_SLASHES);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
@ -6,11 +6,10 @@ email, or any other method with the owners of this repository before making a ch
|
||||
Please note we have a code of conduct, please follow it in all your interactions with the project.
|
||||
|
||||
# Coding Style
|
||||
- We are using [K&R Variant: Linux kernel](https://en.wikipedia.org/wiki/Indentation_style#Variant:_Linux_kernel) with spaces before every `(`.
|
||||
- Every function has a docblock explaining what it does and stating the author, parameters, types, return and exceptions
|
||||
- We use snake_case
|
||||
- We follow every [PSR-2](https://www.php-fig.org/psr/psr-2/) ...
|
||||
- ... except camelCase, that's too bad, we use snake_case
|
||||
|
||||
## Pull Request Process
|
||||
## Merge Request Process
|
||||
|
||||
1. Ensure you strip any trailing spaces off
|
||||
2. Increase the version numbers in any examples files and the README.md to the new version that this
|
||||
|
@ -67,4 +67,3 @@ License along with this program, in the file "COPYING". If not, see
|
||||
to your users under the same license. This is a legal requirement
|
||||
of using the software, and if you do not wish to share your
|
||||
modifications, *YOU MAY NOT USE THIS PLUGIN*.
|
||||
|
||||
|
@ -51,13 +51,11 @@ class apActorFollowersAction extends ManagedAction
|
||||
*/
|
||||
protected function handle()
|
||||
{
|
||||
$nickname = $this->trimmed ('nickname');
|
||||
try {
|
||||
$user = User::getByNickname ($nickname);
|
||||
$profile = $user->getProfile ();
|
||||
$url = $profile->profileurl;
|
||||
$profile = Profile::getByID($this->trimmed('id'));
|
||||
$url = ActivityPubPlugin::actor_url($profile);
|
||||
} catch (Exception $e) {
|
||||
ActivityPubReturn::error ('Invalid username.');
|
||||
ActivityPubReturn::error('Invalid Actor URI.', 404);
|
||||
}
|
||||
|
||||
if (!isset($_GET["page"])) {
|
||||
|
@ -51,13 +51,11 @@ class apActorFollowingAction extends ManagedAction
|
||||
*/
|
||||
protected function handle()
|
||||
{
|
||||
$nickname = $this->trimmed ('nickname');
|
||||
try {
|
||||
$user = User::getByNickname ($nickname);
|
||||
$profile = $user->getProfile ();
|
||||
$url = $profile->profileurl;
|
||||
$profile = Profile::getByID($this->trimmed('id'));
|
||||
$url = ActivityPubPlugin::actor_url($profile);
|
||||
} catch (Exception $e) {
|
||||
ActivityPubReturn::error ('Invalid username.');
|
||||
ActivityPubReturn::error('Invalid Actor URI.', 404);
|
||||
}
|
||||
|
||||
if (!isset($_GET["page"])) {
|
||||
|
@ -51,13 +51,10 @@ class apActorInboxAction extends ManagedAction
|
||||
*/
|
||||
protected function handle()
|
||||
{
|
||||
$nickname = $this->trimmed ('nickname');
|
||||
try {
|
||||
$user = User::getByNickname ($nickname);
|
||||
$profile = $user->getProfile ();
|
||||
$url = $profile->profileurl;
|
||||
$profile = Profile::getByID($this->trimmed('id'));
|
||||
} catch (Exception $e) {
|
||||
ActivityPubReturn::error ("Invalid username.");
|
||||
ActivityPubReturn::error('Invalid Actor URI.', 404);
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
@ -87,7 +84,12 @@ class apActorInboxAction extends ManagedAction
|
||||
ActivityPubReturn::error("Invalid Actor.", 404);
|
||||
}
|
||||
|
||||
$to_profiles = array ($user);
|
||||
// Public To:
|
||||
$public_to = array("https://www.w3.org/ns/activitystreams#Public",
|
||||
"Public",
|
||||
"as:Public");
|
||||
|
||||
$to_profiles = array($profile);
|
||||
|
||||
// Process request
|
||||
switch ($data->type) {
|
||||
@ -109,6 +111,12 @@ class apActorInboxAction extends ManagedAction
|
||||
case "Announce":
|
||||
require_once __DIR__ . DIRECTORY_SEPARATOR . "inbox" . DIRECTORY_SEPARATOR . "Announce.php";
|
||||
break;
|
||||
case "Accept":
|
||||
require_once __DIR__ . DIRECTORY_SEPARATOR . "inbox" . DIRECTORY_SEPARATOR . "Accept.php";
|
||||
break;
|
||||
case "Reject":
|
||||
require_once __DIR__ . DIRECTORY_SEPARATOR . "inbox" . DIRECTORY_SEPARATOR . "Reject.php";
|
||||
break;
|
||||
default:
|
||||
ActivityPubReturn::error("Invalid type value.");
|
||||
}
|
||||
|
152
actions/apactorliked.php
Normal file
152
actions/apactorliked.php
Normal file
@ -0,0 +1,152 @@
|
||||
<?php
|
||||
/**
|
||||
* GNU social - a federating social network
|
||||
*
|
||||
* ActivityPubPlugin implementation for GNU Social
|
||||
*
|
||||
* LICENCE: 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @category Plugin
|
||||
* @package GNUsocial
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @author Daniel Supernault <danielsupernault@gmail.com>
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Actor's Liked Collection
|
||||
*
|
||||
* @category Plugin
|
||||
* @package GNUsocial
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @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 apActorLikedAction extends ManagedAction
|
||||
{
|
||||
protected $needLogin = false;
|
||||
protected $canPost = true;
|
||||
|
||||
/**
|
||||
* Handle the Liked Collection request
|
||||
*
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @return void
|
||||
*/
|
||||
protected function handle()
|
||||
{
|
||||
$nickname = $this->trimmed('nickname');
|
||||
try {
|
||||
$user = User::getByNickname($nickname);
|
||||
$profile = $user->getProfile();
|
||||
$url = $profile->profileurl;
|
||||
} catch (Exception $e) {
|
||||
ActivityPubReturn::error('Invalid username.');
|
||||
}
|
||||
|
||||
$limit = intval($this->trimmed('limit'));
|
||||
$since_id = intval($this->trimmed('since_id'));
|
||||
$max_id = intval($this->trimmed('max_id'));
|
||||
|
||||
$limit = empty($limit) ? 40 : $limit; // Default is 40
|
||||
$since_id = empty($since_id) ? null : $since_id;
|
||||
$max_id = empty($max_id) ? null : $max_id;
|
||||
|
||||
// Max is 80
|
||||
if ($limit > 80) {
|
||||
$limit = 80;
|
||||
}
|
||||
|
||||
$fave = $this->fetch_faves($user->getID(), $limit, $since_id, $max_id);
|
||||
|
||||
$faves = array();
|
||||
while ($fave->fetch()) {
|
||||
$faves[] = $this->pretty_fave(clone ($fave));
|
||||
}
|
||||
|
||||
$res = [
|
||||
'@context' => [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
[
|
||||
"@language" => "en"
|
||||
]
|
||||
],
|
||||
'id' => "{$url}/liked.json",
|
||||
'type' => 'OrderedCollection',
|
||||
'totalItems' => Fave::countByProfile($profile),
|
||||
'orderedItems' => $faves
|
||||
];
|
||||
|
||||
ActivityPubReturn::answer($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* Take a fave object and turns it in a pretty array to be used
|
||||
* as a plugin answer
|
||||
*
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @param Fave $fave_object
|
||||
* @return array pretty array representating a Fave
|
||||
*/
|
||||
protected function pretty_fave($fave_object)
|
||||
{
|
||||
$res = array("uri" => $fave_object->uri,
|
||||
"created" => $fave_object->created,
|
||||
"object" => Activitypub_notice::notice_to_array(Notice::getByID($fave_object->notice_id)));
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch faves
|
||||
*
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @param int32 $user_id
|
||||
* @param int32 $limit
|
||||
* @param int32 $since_id
|
||||
* @param int32 $max_id
|
||||
* @return Fave fetchable fave collection
|
||||
*/
|
||||
private static function fetch_faves(
|
||||
$user_id,
|
||||
$limit = 40,
|
||||
$since_id = null,
|
||||
$max_id = null
|
||||
) {
|
||||
$fav = new Fave();
|
||||
|
||||
$fav->user_id = $user_id;
|
||||
|
||||
$fav->orderBy('modified DESC');
|
||||
|
||||
if ($since_id != null) {
|
||||
$fav->whereAdd("notice_id > {$since_id}");
|
||||
}
|
||||
|
||||
if ($max_id != null) {
|
||||
$fav->whereAdd("notice_id < {$max_id}");
|
||||
}
|
||||
|
||||
$fav->limit($limit);
|
||||
|
||||
$fav->find();
|
||||
|
||||
return $fav;
|
||||
}
|
||||
}
|
@ -1,149 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* GNU social - a federating social network
|
||||
*
|
||||
* ActivityPubPlugin implementation for GNU Social
|
||||
*
|
||||
* LICENCE: 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @category Plugin
|
||||
* @package GNUsocial
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @author Daniel Supernault <danielsupernault@gmail.com>
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Actor's Liked Collection
|
||||
*
|
||||
* @category Plugin
|
||||
* @package GNUsocial
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @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 apActorLikedCollectionAction extends ManagedAction
|
||||
{
|
||||
protected $needLogin = false;
|
||||
protected $canPost = true;
|
||||
|
||||
/**
|
||||
* Handle the Liked Collection request
|
||||
*
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @return void
|
||||
*/
|
||||
protected function handle ()
|
||||
{
|
||||
$nickname = $this->trimmed ('nickname');
|
||||
try {
|
||||
$user = User::getByNickname ($nickname);
|
||||
$profile = $user->getProfile ();
|
||||
$url = $profile->profileurl;
|
||||
} catch (Exception $e) {
|
||||
ActivityPubReturn::error ('Invalid username.');
|
||||
}
|
||||
|
||||
$limit = intval ($this->trimmed ('limit'));
|
||||
$since_id = intval ($this->trimmed ('since_id'));
|
||||
$max_id = intval ($this->trimmed ('max_id'));
|
||||
|
||||
$limit = empty ($limit) ? 40 : $limit; // Default is 40
|
||||
$since_id = empty ($since_id) ? null : $since_id;
|
||||
$max_id = empty ($max_id) ? null : $max_id;
|
||||
|
||||
// Max is 80
|
||||
if ($limit > 80) {
|
||||
$limit = 80;
|
||||
}
|
||||
|
||||
$fave = $this->fetch_faves ($user->getID(), $limit, $since_id, $max_id);
|
||||
|
||||
$faves = array ();
|
||||
while ($fave->fetch ()) {
|
||||
$faves[] = $this->pretty_fave (clone ($fave));
|
||||
}
|
||||
|
||||
$res = [
|
||||
'@context' => [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
[
|
||||
"@language" => "en"
|
||||
]
|
||||
],
|
||||
'id' => "{$url}/liked.json",
|
||||
'type' => 'OrderedCollection',
|
||||
'totalItems' => Fave::countByProfile ($profile),
|
||||
'orderedItems' => $faves
|
||||
];
|
||||
|
||||
ActivityPubReturn::answer ($res);
|
||||
}
|
||||
|
||||
/**
|
||||
* Take a fave object and turns it in a pretty array to be used
|
||||
* as a plugin answer
|
||||
*
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @param Fave $fave_object
|
||||
* @return array pretty array representating a Fave
|
||||
*/
|
||||
protected function pretty_fave ($fave_object)
|
||||
{
|
||||
$res = array("uri" => $fave_object->uri,
|
||||
"created" => $fave_object->created,
|
||||
"object" => Activitypub_notice::notice_to_array (Notice::getByID ($fave_object->notice_id)));
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch faves
|
||||
*
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @param int32 $user_id
|
||||
* @param int32 $limit
|
||||
* @param int32 $since_id
|
||||
* @param int32 $max_id
|
||||
* @return Fave fetchable fave collection
|
||||
*/
|
||||
private static function fetch_faves ($user_id, $limit = 40, $since_id = null,
|
||||
$max_id = null)
|
||||
{
|
||||
$fav = new Fave ();
|
||||
|
||||
$fav->user_id = $user_id;
|
||||
|
||||
$fav->orderBy ('modified DESC');
|
||||
|
||||
if ($since_id != null) {
|
||||
$fav->whereAdd ("notice_id > {$since_id}");
|
||||
}
|
||||
|
||||
if ($max_id != null) {
|
||||
$fav->whereAdd ("notice_id < {$max_id}");
|
||||
}
|
||||
|
||||
$fav->limit ($limit);
|
||||
|
||||
$fav->find ();
|
||||
|
||||
return $fav;
|
||||
}
|
||||
}
|
@ -19,8 +19,8 @@
|
||||
*
|
||||
* @category Plugin
|
||||
* @package GNUsocial
|
||||
* @author Daniel Supernault <danielsupernault@gmail.com>
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @author Daniel Supernault <danielsupernault@gmail.com>
|
||||
* @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/
|
||||
@ -34,7 +34,6 @@ if (!defined ('GNUSOCIAL')) {
|
||||
*
|
||||
* @category Plugin
|
||||
* @package GNUsocial
|
||||
* @author Daniel Supernault <danielsupernault@gmail.com>
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @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/
|
||||
@ -47,19 +46,29 @@ class apActorProfileAction extends ManagedAction
|
||||
/**
|
||||
* Handle the Actor Profile request
|
||||
*
|
||||
* @author Daniel Supernault <danielsupernault@gmail.com>
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @return void
|
||||
*/
|
||||
protected function handle()
|
||||
{
|
||||
$nickname = $this->trimmed ('nickname');
|
||||
if (!empty($id = $this->trimmed('id'))) {
|
||||
try {
|
||||
$user = User::getByNickname ($nickname);
|
||||
$profile = $user->getProfile ();
|
||||
$profile = Profile::getByID($id);
|
||||
} catch (Exception $e) {
|
||||
ActivityPubReturn::error('Invalid Actor URI.', 404);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
unset($id);
|
||||
} else {
|
||||
try {
|
||||
$profile = User::getByNickname($this->trimmed('nickname'))->getProfile();
|
||||
} catch (Exception $e) {
|
||||
ActivityPubReturn::error('Invalid username.', 404);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$profile->isLocal()) {
|
||||
ActivityPubReturn::error("This is not a local user.");
|
||||
}
|
||||
|
||||
$res = Activitypub_profile::profile_to_array($profile);
|
||||
|
||||
|
92
actions/apauthorizeremotefollow.php
Normal file
92
actions/apauthorizeremotefollow.php
Normal file
@ -0,0 +1,92 @@
|
||||
<?php
|
||||
/**
|
||||
* GNU social - a federating social network
|
||||
*
|
||||
* ActivityPubPlugin implementation for GNU Social
|
||||
*
|
||||
* LICENCE: 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @category Plugin
|
||||
* @package GNUsocial
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @author Daniel Supernault <danielsupernault@gmail.com>
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Authorize Remote Follow
|
||||
*
|
||||
* @category Plugin
|
||||
* @package GNUsocial
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @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 apAuthorizeRemoteFollowAction extends Action
|
||||
{
|
||||
/**
|
||||
* Prepare to handle the Authorize Remote Follow request.
|
||||
*
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @param array $args
|
||||
* @return boolean
|
||||
*/
|
||||
protected function prepare(array $args=array())
|
||||
{
|
||||
parent::prepare($args);
|
||||
|
||||
if (!common_logged_in()) {
|
||||
// XXX: selfURL() didn't work. :<
|
||||
common_set_returnto($_SERVER['REQUEST_URI']);
|
||||
if (Event::handle('RedirectToLogin', array($this, null))) {
|
||||
common_redirect(common_local_url('login'), 303);
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
if (!isset($_GET["acct"])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the Authorize Remote Follow Request.
|
||||
*
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
*/
|
||||
protected function handle()
|
||||
{
|
||||
$other = Activitypub_profile::get_from_uri($_GET["acct"]);
|
||||
$actor_profile = common_current_user()->getProfile();
|
||||
$object_profile = $other->local_profile();
|
||||
if (!Subscription::exists($actor_profile, $object_profile)) {
|
||||
Subscription::start($actor_profile, $object_profile);
|
||||
}
|
||||
try {
|
||||
$postman = new Activitypub_postman($actor_profile, [$other]);
|
||||
$postman->follow();
|
||||
} catch (Exception $e) {
|
||||
// Meh, let the exception go on its merry way, it shouldn't be all
|
||||
// that important really.
|
||||
}
|
||||
common_redirect(common_local_url('userbyid', array('id' => $other->profile_id)), 303);
|
||||
}
|
||||
}
|
239
actions/apremotefollow.php
Normal file
239
actions/apremotefollow.php
Normal file
@ -0,0 +1,239 @@
|
||||
<?php
|
||||
/**
|
||||
* GNU social - a federating social network
|
||||
*
|
||||
* ActivityPubPlugin implementation for GNU Social
|
||||
*
|
||||
* LICENCE: 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @category Plugin
|
||||
* @package GNUsocial
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @author Daniel Supernault <danielsupernault@gmail.com>
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remote Follow
|
||||
*
|
||||
* @category Plugin
|
||||
* @package GNUsocial
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @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 apRemoteFollowAction extends Action
|
||||
{
|
||||
public $nickname;
|
||||
public $local_profile;
|
||||
public $remote_identifier;
|
||||
public $err;
|
||||
|
||||
/**
|
||||
* Prepare to handle the Remote Follow request.
|
||||
*
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @param array $args
|
||||
* @return boolean
|
||||
*/
|
||||
protected function prepare(array $args=array())
|
||||
{
|
||||
parent::prepare($args);
|
||||
|
||||
if (common_logged_in()) {
|
||||
// TRANS: Client error.
|
||||
$this->clientError(_m('You can use the local subscription!'));
|
||||
}
|
||||
|
||||
// Local user the remote wants to subscribe to
|
||||
$this->nickname = $this->trimmed('nickname');
|
||||
$this->local_profile = User::getByNickname($this->nickname)->getProfile();
|
||||
|
||||
// Webfinger or profile URL of the remote user
|
||||
$this->remote_identifier = $this->trimmed('remote_identifier');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the Remote Follow Request.
|
||||
*
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
*/
|
||||
protected function handle()
|
||||
{
|
||||
parent::handle();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||
/* Use a session token for CSRF protection. */
|
||||
$token = $this->trimmed('token');
|
||||
if (!$token || $token != common_session_token()) {
|
||||
// TRANS: Client error displayed when the session token does not match or is not given.
|
||||
$this->showForm(_m('There was a problem with your session token. '.
|
||||
'Try again, please.'));
|
||||
return;
|
||||
}
|
||||
$this->activitypub_connect();
|
||||
} else {
|
||||
$this->showForm();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Form.
|
||||
*
|
||||
* @author GNU Social
|
||||
* @param string|null $err
|
||||
*/
|
||||
public function showForm($err = null)
|
||||
{
|
||||
$this->err = $err;
|
||||
if ($this->boolean('ajax')) {
|
||||
$this->startHTML('text/xml;charset=utf-8');
|
||||
$this->elementStart('head');
|
||||
// TRANS: Form title.
|
||||
$this->element('title', null, _m('TITLE', 'Subscribe to user'));
|
||||
$this->elementEnd('head');
|
||||
$this->elementStart('body');
|
||||
$this->showContent();
|
||||
$this->elementEnd('body');
|
||||
$this->endHTML();
|
||||
} else {
|
||||
$this->showPage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Page content.
|
||||
*
|
||||
* @author GNU Social
|
||||
*/
|
||||
public function showContent()
|
||||
{
|
||||
// TRANS: Form legend. %s is a nickname.
|
||||
$header = sprintf(_m('Subscribe to %s'), $this->nickname);
|
||||
// TRANS: Button text to subscribe to a profile.
|
||||
$submit = _m('BUTTON', 'Subscribe');
|
||||
$this->elementStart(
|
||||
'form',
|
||||
['id' => 'form_activitypub_connect',
|
||||
'method' => 'post',
|
||||
'class' => 'form_settings',
|
||||
'action' => common_local_url(
|
||||
'apRemoteFollow',
|
||||
['nickname' => $this->nickname]
|
||||
)
|
||||
]
|
||||
);
|
||||
$this->elementStart('fieldset');
|
||||
$this->element('legend', null, $header);
|
||||
$this->hidden('token', common_session_token());
|
||||
|
||||
$this->elementStart('ul', 'form_data');
|
||||
$this->elementStart('li', array('id' => 'activitypub_nickname'));
|
||||
|
||||
// TRANS: Field label.
|
||||
$this->input(
|
||||
'nickname',
|
||||
_m('User nickname'),
|
||||
$this->nickname,
|
||||
// TRANS: Field title.
|
||||
_m('Nickname of the user you want to follow.')
|
||||
);
|
||||
|
||||
$this->elementEnd('li');
|
||||
$this->elementStart('li', array('id' => 'activitypub_profile'));
|
||||
// TRANS: Field label.
|
||||
$this->input(
|
||||
'remote_identifier',
|
||||
_m('Profile Account'),
|
||||
$this->remote_identifier,
|
||||
// TRANS: Tooltip for field label "Profile Account".
|
||||
_m('Your account ID (e.g. user@example.net).')
|
||||
);
|
||||
$this->elementEnd('li');
|
||||
$this->elementEnd('ul');
|
||||
$this->submit('submit', $submit);
|
||||
$this->elementEnd('fieldset');
|
||||
$this->elementEnd('form');
|
||||
}
|
||||
|
||||
/**
|
||||
* Start connecting the two instances (will be finished with the authorization)
|
||||
*
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @return void
|
||||
*/
|
||||
public function activitypub_connect()
|
||||
{
|
||||
$remote_profile = null;
|
||||
try { // Try with ActivityPub system
|
||||
$remote_profile = Activitypub_profile::get_from_uri($this->remote_identifier);
|
||||
} catch (Exception $e) { // Fallback to compatibility WebFinger system
|
||||
$validate = new Validate();
|
||||
$opts = array('allowed_schemes' => array('http', 'https', 'acct'));
|
||||
if ($validate->uri($this->remote_identifier, $opts)) {
|
||||
$bits = parse_url($this->remote_identifier);
|
||||
if ($bits['scheme'] == 'acct') {
|
||||
$remote_profile = $this->connect_webfinger($bits['path']);
|
||||
}
|
||||
} elseif (strpos($this->remote_identifier, '@') !== false) {
|
||||
$remote_profile = $this->connect_webfinger($this->remote_identifier);
|
||||
}
|
||||
}
|
||||
if (!empty($remote_profile)) {
|
||||
$url = ActivityPubPlugin::stripUrlPath($remote_profile->get_uri())."activitypub/authorize_follow?acct=".$this->local_profile->getUri();
|
||||
common_log(LOG_INFO, "Sending remote subscriber $this->remote_identifier to $url");
|
||||
common_redirect($url, 303);
|
||||
return;
|
||||
}
|
||||
|
||||
// TRANS: Client error.
|
||||
$this->clientError(_m('Must provide a remote profile.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is used by activitypub_connect () and
|
||||
* is a step of the process
|
||||
*
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @param type $acct
|
||||
* @return Profile Profile resulting of WebFinger connection
|
||||
*/
|
||||
private function connect_webfinger($acct)
|
||||
{
|
||||
$link = ActivityPubPlugin::pull_remote_profile($acct);
|
||||
if (!is_null($link)) {
|
||||
return $link;
|
||||
}
|
||||
// TRANS: Client error.
|
||||
$this->clientError(_m('Could not confirm remote profile address.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Page title
|
||||
*
|
||||
* @return string Page title
|
||||
*/
|
||||
public function title()
|
||||
{
|
||||
// TRANS: Page title.
|
||||
return _m('ActivityPub Connect');
|
||||
}
|
||||
}
|
@ -69,7 +69,6 @@ class apSharedInboxAction extends ManagedAction
|
||||
}
|
||||
|
||||
$discovery = new Activitypub_explorer;
|
||||
|
||||
// Get valid Actor object
|
||||
try {
|
||||
$actor_profile = $discovery->lookup($data->actor);
|
||||
@ -77,43 +76,19 @@ class apSharedInboxAction extends ManagedAction
|
||||
} catch (Exception $e) {
|
||||
ActivityPubReturn::error("Invalid Actor.", 404);
|
||||
}
|
||||
|
||||
unset($discovery);
|
||||
|
||||
// Public To:
|
||||
$public_to = array ("https://www.w3.org/ns/activitystreams#Public",
|
||||
$public_to = ["https://www.w3.org/ns/activitystreams#Public",
|
||||
"Public",
|
||||
"as:Public");
|
||||
"as:Public"
|
||||
];
|
||||
|
||||
$to_profiles = "https://www.w3.org/ns/activitystreams#Public";
|
||||
|
||||
// Process request
|
||||
switch ($data->type) {
|
||||
case "Create":
|
||||
if (!isset($data->to)) {
|
||||
ActivityPubReturn::error ("To was not specified.");
|
||||
}
|
||||
$discovery = new Activitypub_explorer;
|
||||
$to_profiles = array ();
|
||||
// Generate To objects
|
||||
if (is_array ($data->to)) {
|
||||
// Remove duplicates from To actors set
|
||||
array_unique ($data->to);
|
||||
foreach ($data->to as $to_url) {
|
||||
try {
|
||||
$to_profiles = array_merge ($to_profiles, $discovery->lookup ($to_url));
|
||||
} catch (Exception $e) {
|
||||
// XXX: Invalid actor found, not sure how we handle those
|
||||
}
|
||||
}
|
||||
} else if (empty ($data->to) || in_array ($data->to, $public_to)) {
|
||||
// No need to do anything else at this point, let's just break out the if
|
||||
} else {
|
||||
try {
|
||||
$to_profiles[]= $discovery->lookup ($data->to);
|
||||
} catch (Exception $e) {
|
||||
ActivityPubReturn::error ("Invalid Actor.", 404);
|
||||
}
|
||||
}
|
||||
unset ($discovery);
|
||||
require_once __DIR__ . DIRECTORY_SEPARATOR . "inbox" . DIRECTORY_SEPARATOR . "Create.php";
|
||||
break;
|
||||
case "Follow":
|
||||
@ -131,6 +106,12 @@ class apSharedInboxAction extends ManagedAction
|
||||
case "Delete":
|
||||
require_once __DIR__ . DIRECTORY_SEPARATOR . "inbox" . DIRECTORY_SEPARATOR . "Delete.php";
|
||||
break;
|
||||
case "Accept":
|
||||
require_once __DIR__ . DIRECTORY_SEPARATOR . "inbox" . DIRECTORY_SEPARATOR . "Accept.php";
|
||||
break;
|
||||
case "Reject":
|
||||
require_once __DIR__ . DIRECTORY_SEPARATOR . "inbox" . DIRECTORY_SEPARATOR . "Reject.php";
|
||||
break;
|
||||
default:
|
||||
ActivityPubReturn::error("Invalid type value.");
|
||||
}
|
||||
|
58
actions/inbox/Accept.php
Normal file
58
actions/inbox/Accept.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
/**
|
||||
* GNU social - a federating social network
|
||||
*
|
||||
* ActivityPubPlugin implementation for GNU Social
|
||||
*
|
||||
* LICENCE: 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @category Plugin
|
||||
* @package GNUsocial
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @author Daniel Supernault <danielsupernault@gmail.com>
|
||||
* @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);
|
||||
}
|
||||
|
||||
// Validate data
|
||||
if (!isset($data->type)) {
|
||||
ActivityPubReturn::error("Type was not specified.");
|
||||
}
|
||||
|
||||
switch ($data->object->type) {
|
||||
case "Follow":
|
||||
// Validate data
|
||||
if (!isset($data->object->object)) {
|
||||
ActivityPubReturn::error("Object Actor URL was not specified.");
|
||||
}
|
||||
// Get valid Object profile
|
||||
try {
|
||||
$object_profile = new Activitypub_explorer;
|
||||
$object_profile = $object_profile->lookup($data->object->object)[0];
|
||||
} catch (Exception $e) {
|
||||
ActivityPubReturn::error("Invalid Object Actor URL.", 404);
|
||||
}
|
||||
|
||||
$pending_list = new Activitypub_pending_follow_requests($actor_profile->getID(), $object_profile->getID());
|
||||
$pending_list->remove();
|
||||
ActivityPubReturn::answer($data); // You are now being followed by this person.
|
||||
break;
|
||||
default:
|
||||
ActivityPubReturn::error("Invalid object type.");
|
||||
break;
|
||||
}
|
@ -30,7 +30,7 @@ if (!defined ('GNUSOCIAL')) {
|
||||
}
|
||||
|
||||
try {
|
||||
Notice::getByUri ($data->object)->repeat ($actor_profile, "ActivityPub");
|
||||
Notice::getByUri($data->object->id)->repeat($actor_profile, "ActivityPub");
|
||||
ActivityPubReturn::answer("Notice repeated successfully.");
|
||||
} catch (Exception $e) {
|
||||
ActivityPubReturn::error($e->getMessage(), 403);
|
||||
|
@ -32,6 +32,9 @@ if (!defined ('GNUSOCIAL')) {
|
||||
$valid_object_types = array("Note");
|
||||
|
||||
// Validate data
|
||||
if (!isset($data->id)) {
|
||||
ActivityPubReturn::error("Id not specified.");
|
||||
}
|
||||
if (!(isset($data->object->type) && in_array($data->object->type, $valid_object_types))) {
|
||||
ActivityPubReturn::error("Invalid Object type.");
|
||||
}
|
||||
@ -43,6 +46,9 @@ if (!isset ($data->object->url)) {
|
||||
} elseif (!filter_var($data->object->url, FILTER_VALIDATE_URL)) {
|
||||
ActivityPubReturn::error("Invalid Object Url.");
|
||||
}
|
||||
if (!isset($data->object->to)) {
|
||||
ActivityPubReturn::error("Object To was not specified.");
|
||||
}
|
||||
|
||||
$content = $data->object->content;
|
||||
|
||||
@ -68,9 +74,34 @@ if (isset ($data->object->reply_to)) {
|
||||
|
||||
$act->context->attention = common_get_attentions($content, $actor_profile, $reply_to);
|
||||
|
||||
foreach ($to_profiles as $to)
|
||||
{
|
||||
$act->context->attention[$to->getUri ()] = "http://activitystrea.ms/schema/1.0/person";
|
||||
$discovery = new Activitypub_explorer;
|
||||
if ($to_profiles == "https://www.w3.org/ns/activitystreams#Public") {
|
||||
$to_profiles = array();
|
||||
}
|
||||
// Generate To objects
|
||||
if (is_array($data->object->to)) {
|
||||
// Remove duplicates from To actors set
|
||||
array_unique($data->object->to);
|
||||
foreach ($data->object->to as $to_url) {
|
||||
try {
|
||||
$to_profiles = array_merge($to_profiles, $discovery->lookup($to_url));
|
||||
} catch (Exception $e) {
|
||||
// XXX: Invalid actor found, not sure how we handle those
|
||||
}
|
||||
}
|
||||
} elseif (empty($data->object->to) || in_array($data->object->to, $public_to)) {
|
||||
// No need to do anything else at this point, let's just break out the if
|
||||
} else {
|
||||
try {
|
||||
$to_profiles[]= $discovery->lookup($data->object->to);
|
||||
} catch (Exception $e) {
|
||||
ActivityPubReturn::error("Invalid Actor.", 404);
|
||||
}
|
||||
}
|
||||
unset($discovery);
|
||||
|
||||
foreach ($to_profiles as $to) {
|
||||
$act->context->attention[ActivityPubPlugin::actor_uri($to)] = "http://activitystrea.ms/schema/1.0/person";
|
||||
}
|
||||
|
||||
// Reject notice if it is too long (without the HTML)
|
||||
@ -79,7 +110,7 @@ if (Notice::contentTooLong ($content)) {
|
||||
ActivityPubReturn::error("That's too long. Maximum notice size is %d character.");
|
||||
}
|
||||
|
||||
$options = array ('source' => 'ActivityPub', 'uri' => $data->id, 'url' => $data->object->url);
|
||||
$options = array('source' => 'ActivityPub', 'uri' => isset($data->id) ? $data->id : $data->object->url, 'url' => $data->object->url);
|
||||
// $options gets filled with possible scoping settings
|
||||
ToSelector::fillActivity($this, $act, $options);
|
||||
|
||||
@ -91,12 +122,11 @@ $actobj->content = common_render_content ($content, $actor_profile, $reply_to);
|
||||
$act->objects[] = $actobj;
|
||||
|
||||
try {
|
||||
$res = array ("@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"id" => $data->id,
|
||||
"url" => $data->object->url,
|
||||
"type" => "Create",
|
||||
"actor" => $data->actor,
|
||||
"object" => Activitypub_notice::notice_to_array (Notice::saveActivity ($act, $actor_profile, $options)));
|
||||
$res = Activitypub_create::create_to_array(
|
||||
$data->id,
|
||||
$data->actor,
|
||||
Activitypub_notice::notice_to_array(Notice::saveActivity($act, $actor_profile, $options))
|
||||
);
|
||||
ActivityPubReturn::answer($res);
|
||||
} catch (Exception $e) {
|
||||
ActivityPubReturn::error($e->getMessage());
|
||||
|
@ -30,12 +30,10 @@ if (!defined ('GNUSOCIAL')) {
|
||||
}
|
||||
|
||||
try {
|
||||
Notice::getByUri ($data->object)->deleteAs ($actor_profile);
|
||||
$res = array ("@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"type" => "Delete",
|
||||
"actor" => $data->actor,
|
||||
"object" => $data->object);
|
||||
ActivityPubReturn::answer ($res);
|
||||
$notice = Notice::getByUri($data->object->id);
|
||||
$notice_to_array = Activitypub_notice::notice_to_array($notice);
|
||||
$notice->deleteAs($actor_profile);
|
||||
ActivityPubReturn::answer(Activitypub_delete::delete_to_array($notice_to_array));
|
||||
} catch (Exception $e) {
|
||||
ActivityPubReturn::error($e->getMessage(), 403);
|
||||
}
|
||||
|
@ -45,11 +45,7 @@ try {
|
||||
try {
|
||||
if (!Subscription::exists($actor_profile, $object_profile)) {
|
||||
Subscription::start($actor_profile, $object_profile);
|
||||
$res = array ("@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"type" => "Follow",
|
||||
"actor" => $data->actor,
|
||||
"object" => $data->object);
|
||||
ActivityPubReturn::answer ($res);
|
||||
ActivityPubReturn::answer(Activitypub_accept::accept_to_array(Activitypub_follow::follow_to_array($data->actor, $data->object)));
|
||||
} else {
|
||||
ActivityPubReturn::error("Already following.", 409);
|
||||
}
|
||||
|
@ -29,13 +29,13 @@ if (!defined ('GNUSOCIAL')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!isset($data->object->id)) {
|
||||
ActivityPubReturn::error("Id not specified.");
|
||||
}
|
||||
|
||||
try {
|
||||
Fave::addNew ($actor_profile, Notice::getByUri ($data->object));
|
||||
$res = array ("@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"type" => "Like",
|
||||
"actor" => $data->actor,
|
||||
"object" => $data->object);
|
||||
ActivityPubReturn::answer ($res);
|
||||
Fave::addNew($actor_profile, Notice::getByUri($data->object->id));
|
||||
ActivityPubReturn::answer(Activitypub_like::like_to_array(Activitypub_notice::notice_to_array($data->actor, json_decode($data->object))));
|
||||
} catch (Exception $e) {
|
||||
ActivityPubReturn::error($e->getMessage(), 403);
|
||||
}
|
||||
|
32
actions/inbox/Reject.php
Normal file
32
actions/inbox/Reject.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
/**
|
||||
* GNU social - a federating social network
|
||||
*
|
||||
* ActivityPubPlugin implementation for GNU Social
|
||||
*
|
||||
* LICENCE: 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @category Plugin
|
||||
* @package GNUsocial
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @author Daniel Supernault <danielsupernault@gmail.com>
|
||||
* @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);
|
||||
}
|
||||
|
||||
// This is a dummy file as there is nothing to do if we fall in this case
|
@ -38,11 +38,21 @@ switch ($data->object->type) {
|
||||
case "Like":
|
||||
try {
|
||||
// Validate data
|
||||
if (!isset ($data->object->object)) {
|
||||
ActivityPubReturn::error ("Object Notice URL was not specified.");
|
||||
if (!isset($data->object->object->id)) {
|
||||
ActivityPubReturn::error("Notice ID was not specified.");
|
||||
}
|
||||
Fave::removeEntry ($actor_profile, Notice::getByUri ($data->object->object));
|
||||
ActivityPubReturn::answer ("Notice disfavorited successfully.");
|
||||
Fave::removeEntry($actor_profile, Notice::getByUri($data->object->object->id));
|
||||
// Notice disfavorited successfully.
|
||||
ActivityPubReturn::answer(
|
||||
Activitypub_undo::undo_to_array(
|
||||
Activitypub_like::like_to_array(
|
||||
Activitypub_notice::notice_to_array(
|
||||
$actor_profile->getUrl(),
|
||||
$data->object->object
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
ActivityPubReturn::error($e->getMessage(), 403);
|
||||
}
|
||||
@ -60,16 +70,22 @@ case "Follow":
|
||||
ActivityPubReturn::error("Invalid Object Actor URL.", 404);
|
||||
}
|
||||
|
||||
try {
|
||||
if (Subscription::exists($actor_profile, $object_profile)) {
|
||||
Subscription::cancel($actor_profile, $object_profile);
|
||||
ActivityPubReturn::answer ("You are no longer following this person.");
|
||||
// You are no longer following this person.
|
||||
ActivityPubReturn::answer(
|
||||
Activitypub_undo::undo_to_array(
|
||||
Activitypub_accept::accept_to_array(
|
||||
Activitypub_follow::follow_to_array(
|
||||
$actor_profile->getUrl(),
|
||||
$object_profile->getUrl()
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
ActivityPubReturn::error("You are not following this person already.", 409);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
ActivityPubReturn::error ("Invalid Object Actor URL.", 404);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ActivityPubReturn::error("Invalid object type.");
|
||||
|
58
classes/Activitypub_accept.php
Normal file
58
classes/Activitypub_accept.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
/**
|
||||
* GNU social - a federating social network
|
||||
*
|
||||
* ActivityPubPlugin implementation for GNU Social
|
||||
*
|
||||
* LICENCE: 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @category Plugin
|
||||
* @package GNUsocial
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @author Daniel Supernault <danielsupernault@gmail.com>
|
||||
* @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 error representation
|
||||
*
|
||||
* @category Plugin
|
||||
* @package GNUsocial
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @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_accept extends Managed_DataObject
|
||||
{
|
||||
/**
|
||||
* Generates an ActivityPub representation of a Accept
|
||||
*
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @param array $object
|
||||
* @return pretty array to be used in a response
|
||||
*/
|
||||
public static function accept_to_array($object)
|
||||
{
|
||||
$res = array("@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"type" => "Accept",
|
||||
"object" => $object
|
||||
);
|
||||
return $res;
|
||||
}
|
||||
}
|
59
classes/Activitypub_announce.php
Normal file
59
classes/Activitypub_announce.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
/**
|
||||
* GNU social - a federating social network
|
||||
*
|
||||
* ActivityPubPlugin implementation for GNU Social
|
||||
*
|
||||
* LICENCE: 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @category Plugin
|
||||
* @package GNUsocial
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @author Daniel Supernault <danielsupernault@gmail.com>
|
||||
* @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 error representation
|
||||
*
|
||||
* @category Plugin
|
||||
* @package GNUsocial
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @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_announce extends Managed_DataObject
|
||||
{
|
||||
/**
|
||||
* Generates an ActivityPub representation of a Announce
|
||||
*
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @param array $object
|
||||
* @return pretty array to be used in a response
|
||||
*/
|
||||
public static function announce_to_array($actor, $object)
|
||||
{
|
||||
$res = array("@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"type" => "Announce",
|
||||
"actor" => $actor,
|
||||
"object" => $object
|
||||
);
|
||||
return $res;
|
||||
}
|
||||
}
|
@ -65,8 +65,7 @@ class Activitypub_attachment extends Managed_DataObject
|
||||
];
|
||||
|
||||
// Image
|
||||
if (substr ($res["mimetype"], 0, 5) == "image")
|
||||
{
|
||||
if (substr($res["mimetype"], 0, 5) == "image") {
|
||||
$res["meta"]= [
|
||||
'width' => $attachment->width,
|
||||
'height' => $attachment->height
|
||||
|
61
classes/Activitypub_create.php
Normal file
61
classes/Activitypub_create.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
/**
|
||||
* GNU social - a federating social network
|
||||
*
|
||||
* ActivityPubPlugin implementation for GNU Social
|
||||
*
|
||||
* LICENCE: 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @category Plugin
|
||||
* @package GNUsocial
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @author Daniel Supernault <danielsupernault@gmail.com>
|
||||
* @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 error representation
|
||||
*
|
||||
* @category Plugin
|
||||
* @package GNUsocial
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @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_create extends Managed_DataObject
|
||||
{
|
||||
/**
|
||||
* Generates an ActivityPub representation of a Create
|
||||
*
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @param string $actor
|
||||
* @param array $object
|
||||
* @return pretty array to be used in a response
|
||||
*/
|
||||
public static function create_to_array($id, $actor, $object)
|
||||
{
|
||||
$res = array("@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"id" => $id,
|
||||
"type" => "Create",
|
||||
"actor" => $actor,
|
||||
"object" => $object
|
||||
);
|
||||
return $res;
|
||||
}
|
||||
}
|
59
classes/Activitypub_delete.php
Normal file
59
classes/Activitypub_delete.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
/**
|
||||
* GNU social - a federating social network
|
||||
*
|
||||
* ActivityPubPlugin implementation for GNU Social
|
||||
*
|
||||
* LICENCE: 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @category Plugin
|
||||
* @package GNUsocial
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @author Daniel Supernault <danielsupernault@gmail.com>
|
||||
* @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 error representation
|
||||
*
|
||||
* @category Plugin
|
||||
* @package GNUsocial
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @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_delete extends Managed_DataObject
|
||||
{
|
||||
/**
|
||||
* Generates an ActivityPub representation of a Delete
|
||||
*
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @param array $object
|
||||
* @return pretty array to be used in a response
|
||||
*/
|
||||
public static function delete_to_array($object)
|
||||
{
|
||||
$res = array("@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"type" => "Delete",
|
||||
"actor" => $object["actor"],
|
||||
"object" => $object
|
||||
);
|
||||
return $res;
|
||||
}
|
||||
}
|
60
classes/Activitypub_follow.php
Normal file
60
classes/Activitypub_follow.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
/**
|
||||
* GNU social - a federating social network
|
||||
*
|
||||
* ActivityPubPlugin implementation for GNU Social
|
||||
*
|
||||
* LICENCE: 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @category Plugin
|
||||
* @package GNUsocial
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @author Daniel Supernault <danielsupernault@gmail.com>
|
||||
* @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 error representation
|
||||
*
|
||||
* @category Plugin
|
||||
* @package GNUsocial
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @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_follow extends Managed_DataObject
|
||||
{
|
||||
/**
|
||||
* Generates an ActivityPub representation of a subscription
|
||||
*
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @param string $actor
|
||||
* @param string $object
|
||||
* @return pretty array to be used in a response
|
||||
*/
|
||||
public static function follow_to_array($actor, $object)
|
||||
{
|
||||
$res = array("@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"type" => "Follow",
|
||||
"actor" => $actor,
|
||||
"object" => $object
|
||||
);
|
||||
return $res;
|
||||
}
|
||||
}
|
60
classes/Activitypub_like.php
Normal file
60
classes/Activitypub_like.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
/**
|
||||
* GNU social - a federating social network
|
||||
*
|
||||
* ActivityPubPlugin implementation for GNU Social
|
||||
*
|
||||
* LICENCE: 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @category Plugin
|
||||
* @package GNUsocial
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @author Daniel Supernault <danielsupernault@gmail.com>
|
||||
* @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 error representation
|
||||
*
|
||||
* @category Plugin
|
||||
* @package GNUsocial
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @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_like extends Managed_DataObject
|
||||
{
|
||||
/**
|
||||
* Generates an ActivityPub representation of a Like
|
||||
*
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @param string $actor
|
||||
* @param array $object
|
||||
* @return pretty array to be used in a response
|
||||
*/
|
||||
public static function like_to_array($actor, $object)
|
||||
{
|
||||
$res = array("@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"type" => "Like",
|
||||
"actor" => $actor,
|
||||
"object" => $object
|
||||
);
|
||||
return $res;
|
||||
}
|
||||
}
|
@ -64,22 +64,22 @@ class Activitypub_notice extends Managed_DataObject
|
||||
}
|
||||
|
||||
$to = array();
|
||||
foreach ($notice->getAttentionProfileIDs () as $to_id) {
|
||||
$to[] = Profile::getById ($to_id)->getUri ();
|
||||
foreach ($notice->getAttentionProfiles() as $to_profile) {
|
||||
$to[] = $to_profile->getUri();
|
||||
}
|
||||
if (!is_null($to)) {
|
||||
if (empty($to)) {
|
||||
$to = array("https://www.w3.org/ns/activitystreams#Public");
|
||||
}
|
||||
|
||||
$item = [
|
||||
'id' => $notice->getUrl (),
|
||||
'type' => 'Notice',
|
||||
'id' => $notice->getUri(),
|
||||
'type' => 'Note',
|
||||
'actor' => $notice->getProfile()->getUrl(),
|
||||
'published' => $notice->getCreated(),
|
||||
'to' => $to,
|
||||
'content' => $notice->getContent(),
|
||||
'url' => $notice->getUrl(),
|
||||
'reply_to' => empty($notice->reply_to) ? null : Notice::getById($notice->reply_to)->getUrl (),
|
||||
'reply_to' => empty($notice->reply_to) ? null : Notice::getById($notice->reply_to)->getUri(),
|
||||
'is_local' => $notice->isLocal(),
|
||||
'conversation' => intval($notice->conversation),
|
||||
'attachment' => $attachments,
|
||||
|
105
classes/Activitypub_pending_follow_requests.php
Normal file
105
classes/Activitypub_pending_follow_requests.php
Normal file
@ -0,0 +1,105 @@
|
||||
<?php
|
||||
/**
|
||||
* GNU social - a federating social network
|
||||
*
|
||||
* ActivityPubPlugin implementation for GNU Social
|
||||
*
|
||||
* LICENCE: 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @category Plugin
|
||||
* @package GNUsocial
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @author Daniel Supernault <danielsupernault@gmail.com>
|
||||
* @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's Pending follow requests
|
||||
*
|
||||
* @category Plugin
|
||||
* @package GNUsocial
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @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_pending_follow_requests extends Managed_DataObject
|
||||
{
|
||||
public $__table = 'Activitypub_pending_follow_requests';
|
||||
public $local_profile_id;
|
||||
public $remote_profile_id;
|
||||
private $_reldb = null;
|
||||
|
||||
/**
|
||||
* Return table definition for Schema setup and DB_DataObject usage.
|
||||
*
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @return array array of column definitions
|
||||
*/
|
||||
public static function schemaDef()
|
||||
{
|
||||
return array(
|
||||
'fields' => array(
|
||||
'local_profile_id' => array('type' => 'integer', 'not null' => true),
|
||||
'remote_profile_id' => array('type' => 'integer', 'not null' => true),
|
||||
'relation_id' => array('type' => 'serial', 'not null' => true),
|
||||
),
|
||||
'primary key' => array('relation_id'),
|
||||
'unique keys' => array(
|
||||
'Activitypub_pending_follow_requests_relation_id_key' => array('relation_id'),
|
||||
),
|
||||
'foreign keys' => array(
|
||||
'Activitypub_pending_follow_requests_local_profile_id_fkey' => array('profile', array('local_profile_id' => 'id')),
|
||||
'Activitypub_pending_follow_requests_remote_profile_id_fkey' => array('profile', array('remote_profile_id' => 'id')),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
public function __construct($actor, $remote_actor)
|
||||
{
|
||||
$this->local_profile_id = $actor;
|
||||
$this->remote_profile_id = $remote_actor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Follow request to table.
|
||||
*
|
||||
* @author Diogo Cordeiro
|
||||
* @param int32 $actor actor id
|
||||
* @param int32 $remote_actor remote actor id
|
||||
*/
|
||||
public function add()
|
||||
{
|
||||
return !$this->exists() && $this->insert();
|
||||
}
|
||||
|
||||
public function exists()
|
||||
{
|
||||
$this->_reldb = clone ($this);
|
||||
if ($this->_reldb->find() > 0) {
|
||||
$this->_reldb->fetch();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function remove()
|
||||
{
|
||||
return $this->exists() && $this->_reldb->delete();
|
||||
}
|
||||
}
|
@ -35,7 +35,6 @@ if (!defined ('GNUSOCIAL')) {
|
||||
* @category Plugin
|
||||
* @package GNUsocial
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @author Daniel Supernault <danielsupernault@gmail.com>
|
||||
* @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/
|
||||
*/
|
||||
@ -51,7 +50,7 @@ class Activitypub_profile extends Profile
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @return array array of column definitions
|
||||
*/
|
||||
static function schemaDef ()
|
||||
public static function schemaDef()
|
||||
{
|
||||
return array(
|
||||
'fields' => array(
|
||||
@ -82,7 +81,8 @@ class Activitypub_profile extends Profile
|
||||
*/
|
||||
public static function profile_to_array($profile)
|
||||
{
|
||||
$url = $profile->getURL ();
|
||||
$uri = ActivityPubPlugin::actor_uri($profile);
|
||||
$id = $profile->getID();
|
||||
$res = [
|
||||
'@context' => [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
@ -90,29 +90,35 @@ class Activitypub_profile extends Profile
|
||||
"@language" => "en"
|
||||
]
|
||||
],
|
||||
'id' => $profile->getID (),
|
||||
'id' => $uri,
|
||||
'type' => 'Person',
|
||||
'nickname' => $profile->getNickname (),
|
||||
'preferredUsername' => $profile->getNickname(),
|
||||
'is_local' => $profile->isLocal(),
|
||||
'inbox' => "{$url}/inbox.json",
|
||||
'sharedInbox' => common_root_url ()."inbox.json",
|
||||
'outbox' => "{$url}/outbox.json",
|
||||
'display_name' => $profile->getFullname (),
|
||||
'followers' => "{$url}/followers.json",
|
||||
'inbox' => common_local_url("apActorInbox", array("id" => $id)),
|
||||
'name' => $profile->getFullname(),
|
||||
'followers' => common_local_url("apActorFollowers", array("id" => $id)),
|
||||
'followers_count' => $profile->subscriberCount(),
|
||||
'following' => "{$url}/following.json",
|
||||
'following' => common_local_url("apActorFollowing", array("id" => $id)),
|
||||
'following_count' => $profile->subscriptionCount(),
|
||||
'liked' => "{$url}/liked.json",
|
||||
'liked' => common_local_url("apActorLiked", array("id" => $id)),
|
||||
'liked_count' => Fave::countByProfile($profile),
|
||||
'summary' => ($desc = $profile->getDescription()) == null ? "" : $desc,
|
||||
'url' => $profile->getURL (),
|
||||
'avatar' => [
|
||||
'icon' => [
|
||||
'type' => 'Image',
|
||||
'width' => 96,
|
||||
'height' => 96,
|
||||
'width' => AVATAR_PROFILE_SIZE,
|
||||
'height' => AVATAR_PROFILE_SIZE,
|
||||
'url' => $profile->avatarUrl(AVATAR_PROFILE_SIZE)
|
||||
]
|
||||
];
|
||||
|
||||
if ($profile->isLocal()) {
|
||||
$res["sharedInbox"] = common_local_url("apSharedInbox", array("id" => $id));
|
||||
} else {
|
||||
$aprofile = new Activitypub_profile();
|
||||
$aprofile = $aprofile->from_profile($profile);
|
||||
$res["sharedInbox"] = $aprofile->sharedInboxuri;
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
@ -129,12 +135,12 @@ class Activitypub_profile extends Profile
|
||||
|
||||
$profile->created = $this->created = $this->modified = common_sql_now();
|
||||
|
||||
$fields = array (
|
||||
$fields = [
|
||||
'uri' => 'profileurl',
|
||||
'nickname' => 'nickname',
|
||||
'fullname' => 'fullname',
|
||||
'bio' => 'bio'
|
||||
);
|
||||
];
|
||||
|
||||
foreach ($fields as $af => $pf) {
|
||||
$profile->$pf = $this->$af;
|
||||
@ -178,7 +184,7 @@ class Activitypub_profile extends Profile
|
||||
* @return Activitypub_profile
|
||||
* @throws Exception if no Activitypub_profile exists for given Profile
|
||||
*/
|
||||
static function from_profile (Profile $profile)
|
||||
public static function from_profile(Profile $profile)
|
||||
{
|
||||
$profile_id = $profile->getID();
|
||||
|
||||
@ -271,10 +277,8 @@ class Activitypub_profile extends Profile
|
||||
if (!empty($profiles_found)) {
|
||||
return self::from_profile($profiles_found[0]);
|
||||
} else {
|
||||
throw new Exception ('No valid ActivityPub profile found for given URI');
|
||||
throw new Exception('No valid ActivityPub profile found for given URI.');
|
||||
}
|
||||
// If it doesn't return a valid Activitypub_profile an exception will
|
||||
// have been thrown before getting to this point.
|
||||
}
|
||||
|
||||
/**
|
||||
@ -325,8 +329,10 @@ class Activitypub_profile extends Profile
|
||||
throw new Exception(_m('Not a valid webfinger address.'));
|
||||
}
|
||||
|
||||
$hints = array_merge (array ('webfinger' => $addr),
|
||||
DiscoveryHints::fromXRD ($xrd));
|
||||
$hints = array_merge(
|
||||
array('webfinger' => $addr),
|
||||
DiscoveryHints::fromXRD($xrd)
|
||||
);
|
||||
|
||||
// If there's an Hcard, let's grab its info
|
||||
if (array_key_exists('hcard', $hints)) {
|
||||
|
58
classes/Activitypub_reject.php
Normal file
58
classes/Activitypub_reject.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
/**
|
||||
* GNU social - a federating social network
|
||||
*
|
||||
* ActivityPubPlugin implementation for GNU Social
|
||||
*
|
||||
* LICENCE: 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @category Plugin
|
||||
* @package GNUsocial
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @author Daniel Supernault <danielsupernault@gmail.com>
|
||||
* @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 error representation
|
||||
*
|
||||
* @category Plugin
|
||||
* @package GNUsocial
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @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_reject extends Managed_DataObject
|
||||
{
|
||||
/**
|
||||
* Generates an ActivityPub representation of a Reject
|
||||
*
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @param array $object
|
||||
* @return pretty array to be used in a response
|
||||
*/
|
||||
public static function reject_to_array($object)
|
||||
{
|
||||
$res = array("@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"type" => "Reject",
|
||||
"object" => $object
|
||||
);
|
||||
return $res;
|
||||
}
|
||||
}
|
59
classes/Activitypub_undo.php
Normal file
59
classes/Activitypub_undo.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
/**
|
||||
* GNU social - a federating social network
|
||||
*
|
||||
* ActivityPubPlugin implementation for GNU Social
|
||||
*
|
||||
* LICENCE: 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @category Plugin
|
||||
* @package GNUsocial
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @author Daniel Supernault <danielsupernault@gmail.com>
|
||||
* @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 error representation
|
||||
*
|
||||
* @category Plugin
|
||||
* @package GNUsocial
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @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_undo extends Managed_DataObject
|
||||
{
|
||||
/**
|
||||
* Generates an ActivityPub representation of a Undo
|
||||
*
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @param array $object
|
||||
* @return pretty array to be used in a response
|
||||
*/
|
||||
public static function undo_to_array($object)
|
||||
{
|
||||
$res = array("@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"type" => "Undo",
|
||||
"actor" => $object["actor"],
|
||||
"object" => $object
|
||||
);
|
||||
return $res;
|
||||
}
|
||||
}
|
25
composer.json
Normal file
25
composer.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "dansup/activity-pub",
|
||||
"description": "ActivityPub plugin for GNU/Social",
|
||||
"type": "gnusocial-plugin",
|
||||
"require": {},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^7.2"
|
||||
},
|
||||
"license": "AGPL",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
"name": "Daniel Supernault",
|
||||
"email": "danielsupernault@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Diogo Cordeiro",
|
||||
"email": "diogo@fc.up.pt"
|
||||
}
|
||||
]
|
||||
}
|
1423
composer.lock
generated
Normal file
1423
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
25
phpunit.xml
Normal file
25
phpunit.xml
Normal file
@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit backupGlobals="false"
|
||||
backupStaticAttributes="false"
|
||||
bootstrap="vendor/autoload.php"
|
||||
colors="true"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
processIsolation="false"
|
||||
stopOnFailure="false">
|
||||
<testsuites>
|
||||
<testsuite name="Feature">
|
||||
<directory suffix="Test.php">./tests/Feature</directory>
|
||||
</testsuite>
|
||||
|
||||
<testsuite name="Unit">
|
||||
<directory suffix="Test.php">./tests/Unit</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<filter>
|
||||
<whitelist processUncoveredFilesFromWhitelist="true">
|
||||
<directory suffix=".php">./app</directory>
|
||||
</whitelist>
|
||||
</filter>
|
||||
</phpunit>
|
28
tests/CreatesApplication.php
Normal file
28
tests/CreatesApplication.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace Tests;
|
||||
|
||||
trait CreatesApplication
|
||||
{
|
||||
/**
|
||||
* Creates the application.
|
||||
*
|
||||
* @return todo
|
||||
*/
|
||||
public static function createApplication()
|
||||
{
|
||||
if (!defined('INSTALLDIR')) {
|
||||
define('INSTALLDIR', __DIR__ . '/../../../');
|
||||
}
|
||||
if (!defined('GNUSOCIAL')) {
|
||||
define('GNUSOCIAL', true);
|
||||
}
|
||||
if (!defined('STATUSNET')) {
|
||||
define('STATUSNET', true); // compatibility
|
||||
}
|
||||
|
||||
require INSTALLDIR . '/lib/common.php';
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
15
tests/TestCase.php
Normal file
15
tests/TestCase.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Tests;
|
||||
|
||||
use PHPUnit\Framework\TestCase as BaseTestCase;
|
||||
|
||||
abstract class TestCase extends BaseTestCase
|
||||
{
|
||||
use CreatesApplication;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->createApplication();
|
||||
}
|
||||
}
|
18
tests/Unit/ExampleTest.php
Normal file
18
tests/Unit/ExampleTest.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use Tests\TestCase;
|
||||
|
||||
class ExampleTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* A basic test example.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testBasicTest()
|
||||
{
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
}
|
29
tests/Unit/ProfileObjectTest.php
Normal file
29
tests/Unit/ProfileObjectTest.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use Tests\TestCase;
|
||||
|
||||
class ProfileObjectTest extends TestCase
|
||||
{
|
||||
public function testLibraryInstalled()
|
||||
{
|
||||
$this->assertTrue(class_exists('\Activitypub_profile'));
|
||||
}
|
||||
|
||||
public function testProfileObject()
|
||||
{
|
||||
// Mimic proper ACCEPT header
|
||||
$_SERVER['HTTP_ACCEPT'] = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams';
|
||||
|
||||
// Fetch profile
|
||||
$user = \Profile::getKV('id', 1);
|
||||
// Fetch ActivityPub Actor Object representation
|
||||
$profile = \Activitypub_profile::profile_to_array($user);
|
||||
|
||||
$this->assertTrue(is_array($profile));
|
||||
|
||||
$this->assertTrue(isset($profile['inbox']));
|
||||
$this->assertTrue(isset($profile['outbox']));
|
||||
}
|
||||
}
|
@ -28,8 +28,9 @@ if (!defined ('GNUSOCIAL')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
class DiscoveryHints {
|
||||
static function fromXRD(XML_XRD $xrd)
|
||||
class DiscoveryHints
|
||||
{
|
||||
public static function fromXRD(XML_XRD $xrd)
|
||||
{
|
||||
$hints = array();
|
||||
|
||||
@ -60,7 +61,7 @@ class DiscoveryHints {
|
||||
return $hints;
|
||||
}
|
||||
|
||||
static function fromHcardUrl($url)
|
||||
public static function fromHcardUrl($url)
|
||||
{
|
||||
$client = new HTTPClient();
|
||||
$client->setHeader('Accept', 'text/html,application/xhtml+xml');
|
||||
@ -76,11 +77,13 @@ class DiscoveryHints {
|
||||
return null;
|
||||
}
|
||||
|
||||
return self::hcardHints($response->getBody(),
|
||||
$response->getEffectiveUrl());
|
||||
return self::hcardHints(
|
||||
$response->getBody(),
|
||||
$response->getEffectiveUrl()
|
||||
);
|
||||
}
|
||||
|
||||
static function hcardHints($body, $url)
|
||||
public static function hcardHints($body, $url)
|
||||
{
|
||||
$hcard = self::_hcard($body, $url);
|
||||
|
||||
@ -119,7 +122,7 @@ class DiscoveryHints {
|
||||
return $hints;
|
||||
}
|
||||
|
||||
static function _hcard($body, $url)
|
||||
public static function _hcard($body, $url)
|
||||
{
|
||||
$mf2 = new Mf2\Parser($body, $url);
|
||||
$mf2 = $mf2->parse();
|
||||
|
@ -74,44 +74,75 @@ class Activitypub_explorer
|
||||
// First check if we already have it locally and, if so, return it
|
||||
// If the local fetch fails: grab it remotely, store locally and return
|
||||
if (! ($this->grab_local_user($url) || $this->grab_remote_user($url))) {
|
||||
throw new Exception ("User not found");
|
||||
throw new Exception("User not found.");
|
||||
}
|
||||
|
||||
|
||||
return $this->discovered_actor_profiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* This ensures that we are using a valid ActivityPub URI
|
||||
*
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @param string $url
|
||||
* @return boolean success state (related to the response)
|
||||
* @throws Exception (If the HTTP request fails)
|
||||
*/
|
||||
private function ensure_proper_remote_uri($url)
|
||||
{
|
||||
$client = new HTTPClient();
|
||||
$headers = array();
|
||||
$headers[] = 'Accept: application/ld+json; profile="https://www.w3.org/ns/activitystreams"';
|
||||
$headers[] = 'User-Agent: GNUSocialBot v0.1 - https://gnu.io/social';
|
||||
$response = $client->get($url, $headers);
|
||||
if (!$response->isOk()) {
|
||||
throw new Exception("Invalid Actor URL.");
|
||||
}
|
||||
$res = json_decode($response->getBody(), JSON_UNESCAPED_SLASHES);
|
||||
if (self::validate_remote_response($res)) {
|
||||
$this->temp_res = $res;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a local user profiles from its URL and joins it on
|
||||
* $this->discovered_actor_profiles
|
||||
*
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @param string $url User's url
|
||||
* @param string $uri Actor's uri
|
||||
* @return boolean success state
|
||||
*/
|
||||
private function grab_local_user ($url)
|
||||
private function grab_local_user($uri)
|
||||
{
|
||||
if (($actor_profile = self::get_profile_by_url ($url)) != false) {
|
||||
$this->discovered_actor_profiles[]= $actor_profile;
|
||||
return true;
|
||||
// Ensure proper remote URI
|
||||
// If an exceptiong ocurrs here it's better to just leave everything
|
||||
// break than to continue processing
|
||||
if ($this->ensure_proper_remote_uri($uri)) {
|
||||
$uri = $this->temp_res["id"];
|
||||
}
|
||||
try {
|
||||
// Try standard ActivityPub route
|
||||
$aprofile = Activitypub_profile::getKV("uri", $uri);
|
||||
if ($aprofile instanceof Activitypub_profile) {
|
||||
$profile = $aprofile->local_profile();
|
||||
} else {
|
||||
/******************************** XXX: ********************************
|
||||
* Sometimes it is not true that the user is not locally available, *
|
||||
* mostly when it is a local user and URLs slightly changed *
|
||||
* e.g.: GS instance owner changed from standard urls to pretty urls *
|
||||
* (not sure if this is necessary, but anyway) *
|
||||
**********************************************************************/
|
||||
// This potential local user is not a remote user.
|
||||
// Let's check for pure blood!
|
||||
$profile = User::getByNickname($this->temp_res["preferredUsername"])->getProfile();
|
||||
}
|
||||
|
||||
// Iff we really are in the same instance
|
||||
$root_url_len = strlen (common_root_url ());
|
||||
if (substr ($url, 0, $root_url_len) == common_root_url ()) {
|
||||
// Grab the nickname and try to get the user
|
||||
if (($actor_profile = Profile::getKV ("nickname", substr ($url, $root_url_len))) != false) {
|
||||
$this->discovered_actor_profiles[]= $actor_profile;
|
||||
// We found something!
|
||||
$this->discovered_actor_profiles[]= $profile;
|
||||
unset($this->temp_res); // IMPORTANT to avoid _dangerous_ noise in the Explorer system
|
||||
return true;
|
||||
} catch (Exception $e) {
|
||||
// We can safely ignore every exception here as we are return false
|
||||
// when it fails the lookup for existing local representation
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -125,6 +156,7 @@ class Activitypub_explorer
|
||||
*/
|
||||
private function grab_remote_user($url)
|
||||
{
|
||||
if (!isset($this->temp_res)) {
|
||||
$client = new HTTPClient();
|
||||
$headers = array();
|
||||
$headers[] = 'Accept: application/ld+json; profile="https://www.w3.org/ns/activitystreams"';
|
||||
@ -134,6 +166,10 @@ class Activitypub_explorer
|
||||
throw new Exception("Invalid Actor URL.");
|
||||
}
|
||||
$res = json_decode($response->getBody(), JSON_UNESCAPED_SLASHES);
|
||||
} else {
|
||||
$res = $this->temp_res;
|
||||
unset($this->temp_res);
|
||||
}
|
||||
if (isset($res["orderedItems"])) { // It's a potential collection of actors!!!
|
||||
foreach ($res["orderedItems"] as $profile) {
|
||||
if ($this->_lookup($profile) == false) {
|
||||
@ -163,9 +199,9 @@ class Activitypub_explorer
|
||||
private function store_profile($res)
|
||||
{
|
||||
$aprofile = new Activitypub_profile;
|
||||
$aprofile->uri = $res["url"];
|
||||
$aprofile->nickname = $res["nickname"];
|
||||
$aprofile->fullname = $res["display_name"];
|
||||
$aprofile->uri = $res["id"];
|
||||
$aprofile->nickname = $res["preferredUsername"];
|
||||
$aprofile->fullname = $res["name"];
|
||||
$aprofile->bio = substr($res["summary"], 0, 1000);
|
||||
$aprofile->inboxuri = $res["inbox"];
|
||||
$aprofile->sharedInboxuri = isset($res["sharedInbox"]) ? $res["sharedInbox"] : $res["inbox"];
|
||||
@ -185,39 +221,13 @@ class Activitypub_explorer
|
||||
*/
|
||||
private static function validate_remote_response($res)
|
||||
{
|
||||
if (!isset ($res["url"], $res["nickname"], $res["display_name"], $res["summary"], $res["inbox"])) {
|
||||
if (!isset($res["id"], $res["preferredUsername"], $res["name"], $res["summary"], $res["inbox"])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a profile from it's profileurl
|
||||
* Unfortunately GNU Social cache is not truly reliable when handling
|
||||
* potential ActivityPub remote profiles, as so it is important to use
|
||||
* this hacky workaround (at least for now)
|
||||
*
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @param string $v URL
|
||||
* @return boolean|Profile false if fails | Profile object if successful
|
||||
*/
|
||||
public static function get_profile_by_url ($v)
|
||||
{
|
||||
$i = Managed_DataObject::getcached("Profile", "profileurl", $v);
|
||||
if (empty ($i)) { // false = cache miss
|
||||
$i = new Profile;
|
||||
$result = $i->get ("profileurl", $v);
|
||||
if ($result) {
|
||||
// Hit!
|
||||
$i->encache();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return $i;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a valid actor profile url returns its inboxes
|
||||
*
|
||||
|
@ -44,7 +44,7 @@ if (!defined ('GNUSOCIAL')) {
|
||||
class Activitypub_postman
|
||||
{
|
||||
private $actor;
|
||||
private $to = array ();
|
||||
private $to = [];
|
||||
private $client;
|
||||
private $headers;
|
||||
|
||||
@ -55,12 +55,12 @@ class Activitypub_postman
|
||||
* @param Profile of sender
|
||||
* @param Activitypub_profile $to array of destinataries
|
||||
*/
|
||||
public function __construct ($from, $to = array ())
|
||||
public function __construct($from, $to = [])
|
||||
{
|
||||
$this->client = new HTTPClient();
|
||||
$this->actor = $from;
|
||||
$this->to = $to;
|
||||
$this->headers = array();
|
||||
$this->headers = [];
|
||||
$this->headers[] = 'Accept: application/ld+json; profile="https://www.w3.org/ns/activitystreams"';
|
||||
$this->headers[] = 'User-Agent: GNUSocialBot v0.1 - https://gnu.io/social';
|
||||
}
|
||||
@ -69,15 +69,28 @@ class Activitypub_postman
|
||||
* Send a follow notification to remote instance
|
||||
*
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @throws Exception
|
||||
*/
|
||||
public function follow()
|
||||
{
|
||||
$data = array ("@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"type" => "Follow",
|
||||
"actor" => $this->actor->getUrl (),
|
||||
"object" => $this->to[0]->getUrl ());
|
||||
$data = Activitypub_follow::follow_to_array($this->actor->getUrl(), $this->to[0]->getUrl());
|
||||
$this->client->setBody(json_encode($data));
|
||||
$this->client->post ($this->to[0]->get_inbox (), $this->headers);
|
||||
$res = $this->client->post($this->to[0]->get_inbox(), $this->headers);
|
||||
$res_body = json_decode($res->getBody());
|
||||
|
||||
if ($res->isOk() || $res->getStatus() == 409) {
|
||||
$pending_list = new Activitypub_pending_follow_requests($this->actor->getID(), $this->to[0]->getID());
|
||||
if (! ($res->getStatus() == 409 || $res_body->type == "Accept")) {
|
||||
$pending_list->add();
|
||||
throw new Exception("Your follow request is pending acceptation.");
|
||||
}
|
||||
$pending_list->remove();
|
||||
return true;
|
||||
} elseif (isset($res_body[0]->error)) {
|
||||
throw new Exception($res_body[0]->error);
|
||||
}
|
||||
|
||||
throw new Exception("An unknown error occurred.");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -87,16 +100,25 @@ class Activitypub_postman
|
||||
*/
|
||||
public function undo_follow()
|
||||
{
|
||||
$data = array ("@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"type" => "Undo",
|
||||
"actor" => $this->actor->getUrl (),
|
||||
"object" => array (
|
||||
"type" => "Follow",
|
||||
"object" => $this->to[0]->getUrl ()
|
||||
$data = Activitypub_undo::undo_to_array(
|
||||
Activitypub_follow::follow_to_array(
|
||||
$this->actor->getUrl(),
|
||||
$this->to[0]->getUrl()
|
||||
)
|
||||
);
|
||||
$this->client->setBody(json_encode($data));
|
||||
$this->client->post ($this->to[0]->get_inbox (), $this->headers);
|
||||
$res = $this->client->post($this->to[0]->get_inbox(), $this->headers);
|
||||
$res_body = json_decode($res->getBody());
|
||||
|
||||
if ($res->isOk() || $res->getStatus() == 409) {
|
||||
$pending_list = new Activitypub_pending_follow_requests($this->actor->getID(), $this->to[0]->getID());
|
||||
$pending_list->remove();
|
||||
return true;
|
||||
}
|
||||
if (isset($res_body[0]->error)) {
|
||||
throw new Exception($res_body[0]->error);
|
||||
}
|
||||
throw new Exception("An unknown error occurred.");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -107,10 +129,10 @@ class Activitypub_postman
|
||||
*/
|
||||
public function like($notice)
|
||||
{
|
||||
$data = array ("@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"type" => "Like",
|
||||
"actor" => $this->actor->getUrl (),
|
||||
"object" => $notice->getUri ());
|
||||
$data = Activitypub_like::like_to_array(
|
||||
$this->actor->getUrl(),
|
||||
Activitypub_notice::notice_to_array($notice)
|
||||
);
|
||||
$this->client->setBody(json_encode($data));
|
||||
foreach ($this->to_inbox() as $inbox) {
|
||||
$this->client->post($inbox, $this->headers);
|
||||
@ -125,12 +147,10 @@ class Activitypub_postman
|
||||
*/
|
||||
public function undo_like($notice)
|
||||
{
|
||||
$data = array ("@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"type" => "Undo",
|
||||
"actor" => $this->actor->getUrl (),
|
||||
"object" => array (
|
||||
"type" => "Like",
|
||||
"object" => $notice->getUri ()
|
||||
$data = Activitypub_undo::undo_to_array(
|
||||
Activitypub_like::like_to_array(
|
||||
$this->actor->getUrl(),
|
||||
Activitypub_notice::notice_to_array($notice)
|
||||
)
|
||||
);
|
||||
$this->client->setBody(json_encode($data));
|
||||
@ -139,28 +159,6 @@ class Activitypub_postman
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a Announce notification to remote instances
|
||||
*
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @param Notice $notice
|
||||
*/
|
||||
public function announce ($notice)
|
||||
{
|
||||
$data = array ("@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"id" => $notice->getUri (),
|
||||
"url" => $notice->getUrl (),
|
||||
"type" => "Announce",
|
||||
"actor" => $this->actor->getUrl (),
|
||||
"to" => "https://www.w3.org/ns/activitystreams#Public",
|
||||
"object" => $notice->getUri ()
|
||||
);
|
||||
$this->client->setBody (json_encode ($data));
|
||||
foreach ($this->to_inbox () as $inbox) {
|
||||
$this->client->post ($inbox, $this->headers);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a Create notification to remote instances
|
||||
*
|
||||
@ -169,16 +167,10 @@ class Activitypub_postman
|
||||
*/
|
||||
public function create($notice)
|
||||
{
|
||||
$data = array ("@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"id" => $notice->getUri (),
|
||||
"type" => "Create",
|
||||
"actor" => $this->actor->getUrl (),
|
||||
"to" => "https://www.w3.org/ns/activitystreams#Public",
|
||||
"object" => array (
|
||||
"type" => "Note",
|
||||
"url" => $notice->getUrl (),
|
||||
"content" => $notice->getContent ()
|
||||
)
|
||||
$data = Activitypub_create::create_to_array(
|
||||
$notice->getUri(),
|
||||
$this->actor->getUrl(),
|
||||
Activitypub_notice::notice_to_array($notice)
|
||||
);
|
||||
if (isset($notice->reply_to)) {
|
||||
$data["object"]["reply_to"] = $notice->getParent()->getUri();
|
||||
@ -189,6 +181,24 @@ class Activitypub_postman
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a Announce notification to remote instances
|
||||
*
|
||||
* @author Diogo Cordeiro <diogo@fc.up.pt>
|
||||
* @param Notice $notice
|
||||
*/
|
||||
public function announce($notice)
|
||||
{
|
||||
$data = Activitypub_announce::announce_to_array(
|
||||
$this->actor->getUrl(),
|
||||
Activitypub_notice::notice_to_array($notice)
|
||||
);
|
||||
$this->client->setBody(json_encode($data));
|
||||
foreach ($this->to_inbox() as $inbox) {
|
||||
$this->client->post($inbox, $this->headers);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a Delete notification to remote instances holding the notice
|
||||
*
|
||||
@ -197,14 +207,22 @@ class Activitypub_postman
|
||||
*/
|
||||
public function delete($notice)
|
||||
{
|
||||
$data = array ("@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"type" => "Delete",
|
||||
"actor" => $this->actor->getUrl (),
|
||||
"object" => $notice->getUri ()
|
||||
);
|
||||
$data = Activitypub_delete::delete_to_array(Activitypub_notice::notice_to_array($notice));
|
||||
$this->client->setBody(json_encode($data));
|
||||
$errors = array();
|
||||
foreach ($this->to_inbox() as $inbox) {
|
||||
$this->client->post ($inbox, $this->headers);
|
||||
$res = $this->client->post($inbox, $this->headers);
|
||||
if (!$res->isOk()) {
|
||||
$res_body = json_decode($res->getBody());
|
||||
if (isset($res_body[0]->error)) {
|
||||
$errors[] = ($res_body[0]->error);
|
||||
continue;
|
||||
}
|
||||
$errors[] = ("An unknown error occurred.");
|
||||
}
|
||||
}
|
||||
if (!empty($errors)) {
|
||||
throw new Exception(json_encode($errors));
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user