Basic follow support

merge-requests/5/head
Terence Eden 2024-02-29 14:46:27 +00:00
rodzic 195be1afea
commit b69c08e5bb
1 zmienionych plików z 116 dodań i 5 usunięć

121
index.php
Wyświetl plik

@ -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
<!DOCTYPE html>
<html lang="en-GB">
<head>
<meta charset="UTF-8">
<title>Follow</title>
<style>
*{font-family:sans-serif;font-size:1.1em;}
</style>
</head>
<body>
<form action="/follow_user" method="post" enctype="multipart/form-data">
<label for="user">User to follow</label>
<input name="user" id="user" type="text" size="32" placeholder="@user@example.com" /><br>
<label for="password">Password</label><br>
<input name="password" id="password" type="password" size="32"><br>
<input type="submit" value="Post Message">
</form>
</body>
</html>
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