Skip to main content






Revealed: a #crypto #billionaireโ€™s political base hosting โ€˜anti-wokeโ€™ and #rightwing activists in Westminster | #Politics | The Guardian


Convicted man moves abroad and then tries to disrupt his native country from afar on #immigration (hypocrite much?) and #abortion.

theguardian.com/politics/2026/โ€ฆ

#UK


tend2wobble reshared this.









"Encyclopedia Britannica and Merriam-Webster have filed a lawsuit against OpenAI, alleging in its complaint that the AI giant has committed 'massive copyright infringement.'" techcrunch.com/2026/03/16/merrโ€ฆ



Andrew Pam reshared this.


friendica (DFRN) - Link to source

Wammawink Centaurworld is fire โ€“ @bbritto-art on Tumblr


tumblr.com/bbritto-art/8107519โ€ฆ

#taur

#taur








I hate hold music, on the phone with the second investment firm, with a good hour so far between the two of them. Of the two, T Rowe Price has the worst hold music.

Vik was so disorganized, so far since he passed in July, I've had to deal with Fidelity, Morgan Stanley (I hate them with a passion), Merril Lynch (not impressed by them, despite knowing there was an account there, likely inherited from his mom, they can't find any record of either of them having an account there ever), and T Rowe Price. Definitely glad to be consolidating this down to a single financial planner who uses Charles Schwab. Not sure how good CS is, but if I had to choose one of the ones I've dealt with, it'd be Fidelity hands down, they've been incredibly easy to deal with.

#Life #Investments #Estate




Wow, what a trainwreck#AI #law #lawfailure #ca #california

reshared this








โ™ฒ @psych@diaspora.glasswings.com:
Contrast & Compare Department Memo # 47

No wonder the orange toddler is unhinged once again.


"The Obamas are competent, classy, loving, Harvard & Princeton graduates, loved by many, not on the #Epstein files, got the #Nobel Peace Prize, & gave us a steady economy to boot."

Aside from today's #unhinged #WordSalad rants (and yesterday's and day before's) - even while hiding out in Mar-a-Lago....
The old #BrainSpurs seem to be acting up too. Poor Donny #TrumpVirus has zero #ExecutiveFunctioning ability - meaning focus, comprehension, sequencing/planning/reasoning skills - and his repertoire of canned bullying and both-sidesism sound bites, is stale.

#TrumpVirus' florid irrationality is so grotesque & obvious that only his inner circle - the ones wearing oversized clown shoes - think or pretend there's nothing wrong or abnormal. Nothing to see here. Presidents always babble incoherently, start wars without goals or Congress, do 'deals' for the family while starving our children and gutting our society. Right, Donald. Even the #cult is tiring of this.








So, about todayโ€ฆ
Lets see, its -6ยฐC at the moment, but feels like -14 and they just revised the temperatures down to a high of -3. Thank goodness I just need to be at the chiropractor in an hour, then pick up a cup of coffee (St Louis Bread ain't great coffee, but its free coffee), and come home, no more out for today. Today is my first four week chiro visit, they've been three week visits since I hurt m'self, and the next one will be in four, but after that we might go six and see how things go, these visits are basically a permanent thing.
Lets see how many spoons I have left after taking care of more estate matters, gotta call two investment banks and depending on how things go, might have to tell the lawyer to unfile the probate matter and refile as a large estate, which is a double edged sword, more money for me, but instead of 2-3 months, this can take a year or more to resolve. Should do some cleaning, butโ€ฆ Ehhh, we'll see.
Yesterday wasn't much, grocery shopping and household finances done. Oh, slightly broke the DBA registry for Missouri by trying to register โ˜…Generalโ˜… Delivery, while the stars didn't throw an error, they did get replaced by question marks as the system has no idea how to handle that.
Tomorrow will be work of course, thankfully it should be around 5 to 10 degrees warmer than today, but not sure about the wind, I think that'll less so the feels like'll be sane. Unfortunately work won't, Friday they were already making noises about it being so busy they need all drivers. Oh well, long day for me, and I'll be able to do an early start on Wed as they'll likely have routes that never went out.

#Weather #Medical #Today #Life #STL #St-Louis





@gabboman the wafrn dev Next version of Wafrn?


ActivityPub Server in a Single PHP File

shkspr.mobi/blog/2024/02/activโ€ฆ
Any computer program can be designed to run from a single file if you architect it wrong enough!

I wanted to create the simplest possible Fediverse server which can be used as an educational tool to show how ActivityPub / Mastodon works.

The design goals were:

  • Upload a single PHP file to the server.
  • No databases or separate config files.
  • Single Actor (i.e. not multi-user).
  • Allow the Actor to be followed.
  • Post plain-text messages to followers.
  • Be roughly standards compliant.

And those goals have all been met! Check it out on GitLab. I warn you though, it is the nadir of bad coding. There are no tests, bugger-all security, scalability isn't considered, and it is a mess. But it works.

You can follow the test user @[url=https://example.viii.fi/example]example@example.viii.fi[/url]

Architecture


Firstly, I've slightly cheated on my "single file" stipulation. There's an .htaccess file which turns example.com/whatever into example.com/index.php?path=whatever

The index.php file then takes that path and does stuff. It also contains all the configuration variables which is very bad practice.

Rather than using a database, it saves files to disk.

Again, this is not suitable for any real world use. This is an educational tool to help explain the basics of posting messages to the Fediverse. It requires absolutely no dependencies. You do not need to spin up a dockerised hypervisor to manage your node bundles and re-compile everything to WASM. Just FTP the file up to prod and you're done.

Walkthrough


This is a quick ramble through the code. It is reasonably well documented, I hope.

Preamble


This is where you set up your account's name and bio. You also need to provide a public/private keypair. The posting page is protected with a password that also needs to be set here.
PHP // Set up the Actor's information
$username = rawurlencode("example"); // Encoded as it is often used as part of a URl
$realName = "E. Xample. Jr.";
$summary = "Some text about the user.";
$server = $_SERVER["SERVER_NAME"]; // Domain name this is hosted on

// Generate locally or from cryptotools.net/rsagen
// Newlines must be replaced with "\n"
$key_private = "-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----";
$key_public = "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----";

// Password for sending messages
$password = "P4ssW0rd";

Logging


ActivityPub is a "chatty" protocol. This takes all the requests your server receives and saves them in /logs/ as a datestamped text file.
PHP // Get all headers and requests sent to this server
$headers = print_r( getallheaders(), true );
$postData = print_r( $_POST, true );
$getData = print_r( $_GET, true );
$filesData = print_r( $_FILES, true );
$body = json_decode( file_get_contents( "php://input" ), true );
$bodyData = print_r( $body, true );
$requestData = print_r( $_REQUEST, true );
$serverData = print_r( $_SERVER, true );

// Get the type of request - used in the log filename
if ( isset( $body["type"] ) ) {
$type = " " . $body["type"];
} else {
$type = "";
}

// Create a timestamp in ISO 8601 format for the filename
$timestamp = date( "c" );
// Filename for the log
$filename = "{$timestamp}{$type}.txt";

// Save headers and request data to the timestamped file in the logs directory
if( ! is_dir( "logs" ) ) { mkdir( "logs"); }

file_put_contents( "logs/{$filename}",
"Headers: \n$headers \n\n" .
"Body Data: \n$bodyData \n\n" .
"POST Data: \n$postData \n\n" .
"GET Data: \n$getData \n\n" .
"Files Data: \n$filesData \n\n" .
"Request Data:\n$requestData\n\n" .
"Server Data: \n$serverData \n\n"
);

Routing


The .htaccess changes /whatever to /?path=whateverThis runs the function of the path requested.
PHP !empty( $_GET["path"] ) ? $path = $_GET["path"] : die();
switch ($path) {
case ".well-known/webfinger":
webfinger();
case rawurldecode( $username ):
username();
case "following":
following();
case "followers":
followers();
case "inbox":
inbox();
case "write":
write();
case "send":
send();
default:
die();
}

WebFinger


The WebFinger Protocol is used to identify accounts. It is requested with example.com/.well-known/webfinger?resource=acct:username@example.comThis server only has one user, so it ignores the query string and always returns the same details.
PHP function webfinger() {
global $username, $server;

$webfinger = array(
"subject" => "acct:{$username}@{$server}",
"links" => array(
array(
"rel" => "self",
"type" => "application/activity+json",
"href" => "https://{$server}/{$username}"
)
)
);
header( "Content-Type: application/json" );
echo json_encode( $webfinger );
die();
}

Username


Requesting example.com/username returns a JSON document with the user's information.
PHP function username() {
global $username, $realName, $summary, $server, $key_public;

$user = array(
"@context" => [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1"
],
"id" => "https://{$server}/{$username}",
"type" => "Person",
"following" => "https://{$server}/following",
"followers" => "https://{$server}/followers",
"inbox" => "https://{$server}/inbox",
"preferredUsername" => rawurldecode($username),
"name" => "{$realName}",
"summary" => "{$summary}",
"url" => "https://{$server}",
"manuallyApprovesFollowers" => true,
"discoverable" => true,
"published" => "2024-02-12T11:51:00Z",
"icon" => [
"type" => "Image",
"mediaType" => "image/png",
"url" => "https://{$server}/icon.png"
],
"publicKey" => [
"id" => "https://{$server}/{$username}#main-key",
"owner" => "https://{$server}/{$username}",
"publicKeyPem" => $key_public
]
);
header( "Content-Type: application/activity+json" );
echo json_encode( $user );
die();
}

Following & Followers


These JSON documents show how many users are following / followers-of this account. The information here is self-attested. So you can lie and use any number you want.
PHPfunction following() {
global $server;

$following = array(
"@context" => "https://www.w3.org/ns/activitystreams",
"id" => "https://{$server}/following",
"type" => "Collection",
"totalItems" => 0,
"items" =>
[] );
header( "Content-Type: application/activity+json" );
echo json_encode( $following );
die();
}
function followers() {
global $server;
$followers = array(
"@context" => "https://www.w3.org/ns/activitystreams",
"id" => "https://{$server}/followers",
"type" => "Collection",
"totalItems" => 0,
"items" =>
[] );
header( "Content-Type: application/activity+json" );
echo json_encode( $followers );
die();
}

Inbox


The /inbox is the main server. It receives all requests. This server only responds to "Follow" requests. A remote server sends a follow request which is a JSON file saying who they are. This code does not cryptographically validate the headers of the received message. The name of the remote user's server is saved to a file so that future messages can be delivered to it. An accept request is cryptographically signed and POST'd back to the remote server.
PHP function inbox() {
global $body, $server, $username, $key_private;

// Get the message and type
$inbox_message = $body;
$inbox_type = $inbox_message["type"];

// This inbox only responds to follow requests
if ( "Follow" != $inbox_type ) { die(); }

// Get the parameters
$inbox_id = $inbox_message["id"];
$inbox_actor = $inbox_message["actor"];
$inbox_host = parse_url( $inbox_actor, PHP_URL_HOST );

// Does this account have any followers?
if( file_exists( "followers.json" ) ) {
$followers_file = file_get_contents( "followers.json" );
$followers_json = json_decode( $followers_file, true );
} else {
$followers_json = array();
}

// Add user to list. Don't care about duplicate users, server is what's important
$followers_json[$inbox_host]["users"][] = $inbox_actor;

// Save the new followers file
file_put_contents( "followers.json", print_r( json_encode( $followers_json ), true ) );

// Response Message ID
// This isn't used for anything important so could just be a random number
$guid = uuid();

// Create the Accept message
$message = [
"@context" => "https://www.w3.org/ns/activitystreams",
"id" => "https://{$server}/{$guid}",
"type" => "Accept",
"actor" => "https://{$server}/{$username}",
"object" => [
"@context" => "https://www.w3.org/ns/activitystreams",
"id" => $inbox_id,
"type" => $inbox_type,
"actor" => $inbox_actor,
"object" => "https://{$server}/{$username}",
]
];

// The Accept is sent to the server of the user who requested the follow
// TODO: The path doesn't *always* end with/inbox
$host = $inbox_host;
$path = parse_url( $inbox_actor, PHP_URL_PATH ) . "/inbox";

// Get the signed headers
$headers = generate_signed_headers( $message, $host, $path );

// Specify the URL of the remote server's inbox
// TODO: The path doesn't *always* end with /inbox
$remoteServerUrl = $inbox_actor . "/inbox";

// POST the message and header to the requester's inbox
$ch = curl_init( $remoteServerUrl );
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 );
$response = curl_exec( $ch );

// Check for errors
if( curl_errno( $ch ) ) {
file_put_contents( "error.txt", curl_error( $ch ) );
}
curl_close($ch);
die();
}

UUID


Every message sent should have a unique ID. This can be anything you like. Some servers use a random number. I prefer a date-sortable string.
PHP function uuid() {
return sprintf( "%08x-%04x-%04x-%04x-%012x",
time(),
mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0x3fff) | 0x8000,
mt_rand(0, 0xffffffffffff)
);
}

Signing Headers


Every message that your server sends needs to be cryptographically signed with your Private Key. This is a complicated process. Please read "How to make friends and verify requests" for more information.
PHP function generate_signed_headers( $message, $host, $path ) {
global $server, $username, $key_private;

// Encode the message to JSON
$message_json = json_encode( $message );

// Location of the Public Key
$keyId = "https://{$server}/{$username}#main-key";

// Generate signing variables
$hash = hash( "sha256", $message_json, true );
$digest = base64_encode( $hash );
$date = date( "D, d M Y H:i:s \G\M\T" );

// Get the Private Key
$signer = openssl_get_privatekey( $key_private );

// Sign the path, host, date, and digest
$stringToSign = "(request-target): post $path\nhost: $host\ndate: $date\ndigest: SHA-256=$digest";

// The signing function returns the variable $signature
// php.net/manual/en/function.opeโ€ฆ
openssl_sign(
$stringToSign,
$signature,
$signer,
OPENSSL_ALGO_SHA256
);
// Encode the signature
$signature_b64 = base64_encode( $signature );

// Full signature header
$signature_header = 'keyId="' . $keyId . '",algorithm="rsa-sha256",headers="(request-target) host date digest",signature="' . $signature_b64 . '"';

// Header for POST reply
$headers = array(
"Host: {$host}",
"Date: {$date}",
"Digest: SHA-256={$digest}",
"Signature: {$signature_header}",
"Content-Type: application/activity+json",
"Accept: application/activity+json",
);

return $headers;
}

User Interface for Writing


This creates a basic HTML form. Type in your message and your password. It then POSTs the data to the /send endpoint.
PHP function write() {
// Display an HTML form for the user to enter a message.echo <<< HTML
<!DOCTYPE html>
<html lang="en-GB">
<head>
<meta charset="UTF-8">
<title>Send Message</title>
<style>
*{font-family:sans-serif;font-size:1.1em;}
</style>
</head>
<body>
<form action="/send" method="post" enctype="multipart/form-data">
<label for="content">Your message:</label><br>
<textarea id="content" name="content" rows="5" cols="32"></textarea><br>
<label for="password">Password</label><br>
<input type="password" name="password" id="password" size="32"><br>
<input type="submit" value="Post Message">
</form>
</body>
</html>
HTML;
die();
}

Send Endpoint


This takes the submitted message and checks the password is correct. It reads the followers.json file and sends the message to every server that is following this account.
PHP function send() {
global $password, $server, $username, $key_private;

// Does the posted password match the stored password?
if( $password != $_POST["password"] ) { die(); }

// Get the posted content
$content = $_POST["content"];

// Current time - ISO8601
$timestamp = date( "c" );

// Outgoing Message ID
$guid = uuid();

// Construct the Note
// contentMap is used to prevent unnecessary "translate this post" pop ups
// hardcoded to English
$note = [
"@context" => array(
"https://www.w3.org/ns/activitystreams"
),
"id" => "https://{$server}/posts/{$guid}.json",
"type" => "Note",
"published" => $timestamp,
"attributedTo" => "https://{$server}/{$username}",
"content" => $content,
"contentMap" => ["en" => $content],
"to" => ["https://www.w3.org/ns/activitystreams#Public"]
];

// Construct the Message
$message = [
"@context" => "https://www.w3.org/ns/activitystreams",
"id" => "https://{$server}/posts/{$guid}.json",
"type" => "Create",
"actor" => "https://{$server}/{$username}",
"to" => [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc" => [
"https://{$server}/followers"
],
"object" => $note
];

// Create the context for the permalink
$note = [ "@context" => "https://www.w3.org/ns/activitystreams", ...$note ];

// Save the permalink
$note_json = json_encode( $note );
// Check for posts/ directory and create it
if( ! is_dir( "posts" ) ) { mkdir( "posts"); }
file_put_contents( "posts/{$guid}.json", print_r( $note_json, true ) );

// Read existing users and get their hosts
$followers_file = file_get_contents( "followers.json" );
$followers_json = json_decode( $followers_file, true );
$hosts = array_keys( $followers_json );

// Prepare to use the multiple cURL handle
$mh = curl_multi_init();

// Loop through all the severs of the followers
// Each server needs its own cURL handle
// Each POST to an inbox needs to be signed separately
foreach ( $hosts as $host ) {
$path = "/inbox";

// Get the signed headers
$headers = generate_signed_headers( $message, $host, $path );

// Specify the URL of the remote server
$remoteServerUrl = "https://{$host}{$path}";

// POST the message and header to the requester's inbox
$ch = curl_init( $remoteServerUrl );

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 );

// Add the handle to the multi-handle
curl_multi_add_handle( $mh, $ch );
}

// Execute the multi-handle
do {
$status = curl_multi_exec( $mh, $active );
if ( $active ) {
curl_multi_select( $mh );
}
} while ( $active && $status == CURLM_OK );

// Close the multi-handle
curl_multi_close( $mh );

// Render the JSON so the user can see the POST has worked
header( "Location: https://{$server}/posts/{$guid}.json" );
die();
}

Next Steps


This is not intended to be used in production. Ever. But if you would like to contribute more simple examples of how the protocol works, please come and play on GitLab.

You can follow the test user @[url=https://example.viii.fi/example]example@example.viii.fi[/url]
#ActivityPub #mastodon #php


gabboman the wafrn dev reshared this.
















Ok, might not be leaving


So I might be re-focusing here, Wafrn is getting on my nerves. Its not that I don't like it, @gabboman the wafrn dev has done a great job, but I don't like being forced to be linked to Bluesky. Since I disconnected from it I get error message when trying to like, re-share or comment (I think its all of them) on random posts, not just Bluesky or Wafrn, but randomly anything, mastodon.social posts sometimes will throw an error for example.

And then there's the problem with links to accounts, there's two, one in the profile and another at the bottom of the timeline to go to the user's actual page rather than filtered through my instance. Its a minor problem, but especially on .social domains the bottom one substitutes a number for the user and doesn't work.

Sure, there's features it lacks that I'd like, and if'n someone came along and made something akin to Mastodon/Pleroma/Wafrn/et al with those would be awesome, but I can live with what it has.

I am giving GoToSocial a shot, if'n I can get time to really play with it, but at first glance I can't say I'm impressed by it, there's no obvious additions or improvements to it over the other choices, and the lack of a front end means that I'm using basically the same interface as Mastodon, so it doesn't look nor feel any different.

#WAFRN-Problem #wafrn #gotosocial #Fediverse #socialmedia

#GoToSocial reshared this.

in reply to ๐ŸŒด Seph ๐Ÿ’ญ ๐Ÿ‘พ

Just my two cents on GoToSocial. I really loved it when I used to be heavily active on fedi, but its promises of being "lightweight" are somewhatโ€ฆ incorrect. While that's true, it eventually chews through I/O like its no-one's business. Suppose it you keep to a small circle it probably won't be that bad, but letting it run wild federating with a huge amount of servers really chokes it.
in reply to gabboman the wafrn dev

Ours was connected to Postgres. I must point out our GTS server was federating with an insane amount of servers (like, getting way past 10k+). While that probably wasn't within its usecase, it was entirely the reason I stopped self-hosting fedi in the end: I'd already obliterated our self-hosted Mastodon instance, and I just could not be bothered with doing yet-another-migration-and-starting-afresh-again.
in reply to Ducky ๐Ÿฆ†๐Ÿ‡ผ

@Ducky ๐Ÿฆ†๐Ÿ‡ผ @gabboman the wafrn dev Yeah, that was its one claim to fame that I could see, and if'n you're just doing a one user, there's lots of alternatives, though my pet peeve with those is mostly that they're all identical in features with the only difference being what programming language they're in.





L'Amรฉrique et Israรซl ont normalisรฉ le meurtre d'enfants. Les plus innocents, les plus vulnรฉrables, sont massacrรฉs et le monde continue comme si de rien n'รฉtait. ร‡a suffit ! Combien de temps encore le sang des enfants sera-t-il le ยซ problรจme ยป de quelqu'un d'autre ?



friendica (DFRN) - Link to source

Kayden Clef Canine Taur (he/him) He's a sparkledog ๐ŸŒˆ โ€“ @sharpytown on Tumblr


tumblr.com/sharpytown/81083655โ€ฆ

#taur

#taur
โ‡ง