diff --git a/index.php b/index.php
index 1ba614a..9cce61d 100644
--- a/index.php
+++ b/index.php
@@ -106,6 +106,10 @@
send(); // API for posting content to the Fediverse
case "outbox":
outbox(); // Optional. Dynamic.
+ case "follow":
+ follow(); // User interface for following an external user
+ case "follow_user":
+ follow_user(); // API for following a user
case "/":
home(); // Optional. Can be dynamic
default:
@@ -265,10 +269,10 @@
// Close cURL session
curl_close($ch);
- // Save the actor's data in `/data/followers/`
- if( ! is_dir( "data/followers" ) ) { mkdir( "data"); mkdir( "data/followers"); }
- $follower_filename = urlencode( $follower_actor );
- file_put_contents( "data/followers/{$follower_filename}.json", $inbox_actor_json );
+ // Save the actor's data in `/data/followers/`
+ if( ! is_dir( "data/followers" ) ) { mkdir( "data"); mkdir( "data/followers"); }
+ $follower_filename = urlencode( $follower_actor );
+ file_put_contents( "data/followers/{$follower_filename}.json", $inbox_actor_json );
} else {
die();
@@ -713,7 +717,7 @@ HTML;
// This goes in the generic "tag" property
// TODO: Add this to the CC field
foreach ( $usernames as $username ) {
- list( $null, $user, $domain ) = explode( "@", $username );
+ list( , $user, $domain ) = explode( "@", $username );
$tags[] = array(
"type" => "Mention",
"href" => "https://{$domain}/@{$user}",
@@ -773,6 +777,113 @@ HTML;
die();
}
+ // This creates a UI for the user to follow another user
+ function follow() {
+ echo <<< HTML
+
+
+
+
+ Follow
+
+
+
+
+
+
+HTML;
+ die();
+ }
+
+ // This receives a request to follow an external user
+ // It looks up the external user's details
+ // Then it sends a follow request
+ // If the request is accepted, it saves the details in `data/following/` as a JSON file
+ function follow_user() {
+ global $password, $server, $username, $key_private;
+
+ // Does the posted password match the stored password?
+ if( $password != $_POST["password"] ) { echo "Wrong Password!"; die(); }
+
+ // Get the posted content
+ $user = $_POST["user"];
+
+ // Split the user into username and server
+ list( , $follow_name, $follow_server ) = explode( "@", $user );
+
+ // Get the Webfinger
+ $webfingerURl = "https://{$follow_server}/.well-known/webfinger?resource=acct:{$follow_name}@{$follow_server}";
+ $webfingerJSON = file_get_contents( $webfingerURl );
+ $webfinger = json_decode( $webfingerJSON, true );
+
+ foreach( $webfinger["links"] as $link ) {
+ if ( "self" == $link["rel"] ) {
+ $profileURl = $link["href"];
+ }
+ }
+ if ( !isset( $profileURl ) ) { echo "No profile"; die(); }
+
+ // Get the user's details
+ // This request does not need to be signed. But it does need to specify that it wants a JSON response
+ $context = stream_context_create(
+ [ "http" => [ "header" => "Accept: application/activity+json" ] ]
+ );
+ $profileJSON = file_get_contents( $profileURl, false, $context );
+ $profileData = json_decode( $profileJSON, true );
+
+ // Get the user's inbox
+ $profileInbox = $profileData["inbox"];
+ $inbox_host = parse_url( $profileInbox, PHP_URL_HOST );
+ $inbox_path = parse_url( $profileInbox, PHP_URL_PATH );
+
+ // Create a follow request
+ $guid = uuid();
+ $message = [
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "id" => "https://{$server}/{$guid}",
+ "type" => "Follow",
+ "actor" => "https://{$server}/{$username}",
+ "object" => $profileURl
+ ];
+
+ // Sign a request follow
+ // The Accept is POSTed to the inbox on the server of the user who requested the follow
+ // Get the signed headers
+ $headers = generate_signed_headers( $message, $inbox_host, $inbox_path, "POST" );
+
+ // POST the message and header to the requester's inbox
+ $ch = curl_init( $profileInbox );
+ curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
+ curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, "POST" );
+ curl_setopt( $ch, CURLOPT_POSTFIELDS, json_encode( $message ) );
+ curl_setopt( $ch, CURLOPT_HTTPHEADER, $headers );
+ curl_exec( $ch );
+
+ // Check for errors
+ if( curl_errno( $ch ) ) {
+ file_put_contents( "error.txt", curl_error( $ch ) );
+ }
+ curl_close($ch);
+
+ // Save the user's details
+ // Save headers and request data to the timestamped file in the logs directory
+ if( ! is_dir( "data/following" ) ) { mkdir( "data"); mkdir( "data/following"); }
+ $following_filename = urlencode( $profileURl );
+ file_put_contents( "data/following/{$following_filename}.json", $profileJSON );
+
+ // Render the JSON so the user can see the POST has worked
+ header( "Location: https://{$server}/data/following/" . urlencode( $following_filename ) . ".json" );
+ die();
+ }
+
// Verify the signature sent with the message.
// This is optional
// It is very confusing