<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/atom-style.xsl" type="text/xsl"?><feed
	xmlns="http://www.w3.org/2005/Atom"
	xmlns:thr="http://purl.org/syndication/thread/1.0"
	xml:lang="en-GB"
	
	xmlns:georss="http://www.georss.org/georss"
	xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#"
	>
	<title type="text">Terence Eden’s Blog</title>
	<subtitle type="text"></subtitle>

	<updated>2024-02-22T08:44:27Z</updated>

	<link rel="alternate" type="text/html" href="https://shkspr.mobi/blog" />
	<id>https://shkspr.mobi/blog/feed/atom/</id>
	<link rel="self" type="application/atom+xml" href="https://shkspr.mobi/blog/feed/atom/" />

	<generator uri="https://wordpress.org/" version="6.4.3">WordPress</generator>
<icon>https://shkspr.mobi/blog/wp-content/uploads/2023/07/cropped-avatar-32x32.jpeg</icon>
	<entry>
		<author>
			<name>@edent</name>
					</author>

		<title type="html"><![CDATA[Book Review: We Are Bellingcat - Eliot Higgins ★★★⯪☆]]></title>
		<link rel="alternate" type="text/html" href="https://shkspr.mobi/blog/2024/02/book-review-we-are-bellingcat-eliot-higgins/" />

		<id>https://shkspr.mobi/blog/?p=49672</id>
		<updated>2024-02-22T08:44:27Z</updated>
		<published>2024-02-22T12:34:29Z</published>
		<category scheme="https://shkspr.mobi/blog" term="/etc/" /><category scheme="https://shkspr.mobi/blog" term="Book Review" />
		<summary type="html"><![CDATA[The problem with autobiographies is that every anecdote ends with "needless to say, I had the last laugh!" This corporate-autobiography is no different - as it details the rise and impact of Bellingcat - a team of investigators and journalists. I am in awe of Bellingcat - and have seen them give talks on a [&#8230;]]]></summary>

					<content type="html" xml:base="https://shkspr.mobi/blog/2024/02/book-review-we-are-bellingcat-eliot-higgins/"><![CDATA[<p><img decoding="async" src="https://shkspr.mobi/blog/wp-content/uploads/2024/02/bellingcat.jpg" alt="Book cover with an inverted question mark." width="200" class="alignleft size-full wp-image-49673" />The problem with autobiographies is that <em>every</em> anecdote ends with "needless to say, I had the last laugh!"  This corporate-autobiography is no different - as it details the rise and impact of Bellingcat - a team of investigators and journalists.</p>
<p>I am in awe of Bellingcat - and have seen them give talks on a couple of occasions. This book is a thrilling account of how they perform "open source" investigations; solving crimes with freely available data. But every few pages, I got an uneasy feeling about their methods and motivations. The book is surprisingly incurious about the effectiveness, morality, and legality of what they do.</p>
<p>It is an excellent page-turner.  From the origins in the Something Awful forums (!) to a network dedicated to unmasking the shadowy world of international espionage. It is honestly exciting - but strangely voyeuristic.</p>
<p>I don't know how to feel about a sentence like:</p>
<blockquote><p>
  Although I spoke no Arabic and had never visited Syria, I became intimately involved in studying its war, observing civilians’ despair worsening every day via YouTube.
</p></blockquote>
<p>It felt a little "<a href="https://en.wikipedia.org/wiki/White_savior#%22White_Savior_Industrial_Complex%22">White Saviour Industrial Complex</a>" to me. It is impossible to deny just how useful Bellingcat's techniques are, but it feels weird not centring or acknowledging what local people were doing.</p>
<p>I also don't know how to feel about releasing to the public <em>before</em> prosecutors:</p>
<blockquote><p>
  For maximum impact, we had timed publication to the day before a report from the Dutch Safety Board, which had been assigned to determine the cause of the downing, although not to identify any culprits.
</p></blockquote>
<p>There are several instances where it seems that chasing headlines (and A/B testing them) was more important than getting evidence into the right hands.</p>
<p>I can't argue against Bellingcat being a force for good but, again, this makes me uneasy:</p>
<blockquote><p>
  ‘There’s a geolocation challenge for ya,’ I wrote, reposting the Münster photo to the tens of thousands of people who by that stage followed me on Twitter. Resourceful amateurs noted an advertising column in the background, and discovered ...
</p></blockquote>
<p>What's the difference between this and doxxing? Why is it OK for Bellingcat to ask people to help reveal private information about someone? Does it teach the public that this form of journalism is legitimate no matter the target?</p>
<p>It gets murkier:</p>
<blockquote><p>
  In the case of the passenger manifest for the Moscow–London flight, we found somebody online with access to the Russian airlines’ booking-data system and paid about €200 for the file. The flight manifest added two crucial datapoints: the suspects’ purported birthdates and passport numbers.
</p></blockquote>
<p>That seems straight-up illegal to me. They bribed someone to breach the privacy of hundreds of travellers because they <em>suspected</em> their target was on board.  Again, all for the greater good, right?</p>
<blockquote><p>
  A contact at a Russian mobile operator provided us with metadata from a phone number registered under Sergeev’s cover persona, Fedotov. This showed all movements of Sergeev’s phone over the course of two years, from May 2017 to May 2019, including the date of the Skripal attack.
</p></blockquote>
<p>That's not "open source" intelligence. That's closer to industrial espionage.</p>
<p>I found the whole book ethically vague. Their skills are impressive. The crimes they've investigated are horrendous. It is amazing just how easy these data are to obtain. And it is incredible that a small team of loosely-affiliated investigators are able to build up such compelling evidence. But what keeps them honest? How easy would it be for them to turn their ire on someone innocent?</p>
<p><i lang="la">Qui indagatrix inquisitores?</i></p>
]]></content>
		
					<link rel="replies" type="text/html" href="https://shkspr.mobi/blog/2024/02/book-review-we-are-bellingcat-eliot-higgins/#comments" thr:count="0" />
			<link rel="replies" type="application/atom+xml" href="https://shkspr.mobi/blog/2024/02/book-review-we-are-bellingcat-eliot-higgins/feed/atom/" thr:count="0" />
			<thr:total>0</thr:total>
			</entry>
		<entry>
		<author>
			<name>@edent</name>
					</author>

		<title type="html"><![CDATA[It&#039;s a process; not a product]]></title>
		<link rel="alternate" type="text/html" href="https://shkspr.mobi/blog/2024/02/its-a-process-not-a-product/" />

		<id>https://shkspr.mobi/blog/?p=49679</id>
		<updated>2024-02-18T18:15:53Z</updated>
		<published>2024-02-21T12:34:55Z</published>
		<category scheme="https://shkspr.mobi/blog" term="/etc/" /><category scheme="https://shkspr.mobi/blog" term="accessibility" /><category scheme="https://shkspr.mobi/blog" term="security" /><category scheme="https://shkspr.mobi/blog" term="usability" />
		<summary type="html"><![CDATA[Sometimes a client asks me a question and I'm a little stunned by their mental model of the world. A few weeks ago, we were discussing the need for better cybersecurity in their architecture. We spoke about several aspects of security, then they asked an outstanding question. "What should I buy to be secure?" It [&#8230;]]]></summary>

					<content type="html" xml:base="https://shkspr.mobi/blog/2024/02/its-a-process-not-a-product/"><![CDATA[<p>Sometimes a client asks me a question and I'm a little stunned by their mental model of the world.</p>
<p>A few weeks ago, we were discussing the need for better cybersecurity in their architecture. We spoke about several aspects of security, then they asked an <em>outstanding</em> question.</p>
<p>"What should I buy to be secure?"</p>
<p>It took a few moments to tease out exactly what they thought they were asking. In their mental model they could just buy a box which did what they needed. Want to print from any workstation? Buy a big HP network printer. Want to get WiFi in the office? Buy a bunch of access points. Want a website? Buy a WordPress. Want security? Buy a [<em>fill in the blank</em>]?</p>
<p>Their notion is that most things are products.  This is a common belief. I've had clients ask "What do I buy to make this accessible?" or "What can I buy to improve usability?"</p>
<p>In all these cases there <em>are</em> unscrupulous people who will sell you a magic cure-all - but the real answer is that these things are a <em>process</em>; not a product.</p>
<p>Yes, you can buy tools which will help <em>improve</em> your security / accessibility / usability etc. But unless you put processes in place to get people to use them effectively, the tools are useless.</p>
<p>People need to understand <em>why</em> something is important. They need processes which <em>support</em> best practices. The business needs a holistic understanding of <em>how</em> these processes improve the business.  And that is all underpinned by tools which make it possible.</p>
<p>There's no magic box which can both protect you from your CEO accidentally CCing confidential data to a competitor <em>and</em> stop DDoS attacks. An accessibility overlay won't help you if your staff refuse to incorporate alt text into their workflow. Automated code testing can't stop you building things without testing them with users.</p>
<p>Security is a <em>verb</em> - it is a <em>doing</em> word.<br />
Accessibility is a <em>verb</em> - it is a <em>doing</em> word.<br />
Usability is a <em>verb</em> - it is a <em>doing</em> word.</p>
<p>Buy nouns which support your verbs.</p>
<p><iframe class="youtube-player" width="620" height="349" src="https://www.youtube.com/embed/u7K72X4eo_s?version=3&#038;rel=1&#038;showsearch=0&#038;showinfo=1&#038;iv_load_policy=1&#038;fs=1&#038;hl=en-GB&#038;autohide=2&#038;wmode=transparent" allowfullscreen="true" style="border:0;" sandbox="allow-scripts allow-same-origin allow-popups allow-presentation allow-popups-to-escape-sandbox"></iframe></p>
]]></content>
		
					<link rel="replies" type="text/html" href="https://shkspr.mobi/blog/2024/02/its-a-process-not-a-product/#comments" thr:count="3" />
			<link rel="replies" type="application/atom+xml" href="https://shkspr.mobi/blog/2024/02/its-a-process-not-a-product/feed/atom/" thr:count="3" />
			<thr:total>3</thr:total>
			</entry>
		<entry>
		<author>
			<name>@edent</name>
					</author>

		<title type="html"><![CDATA[Minority Governments and the Boundary Commission]]></title>
		<link rel="alternate" type="text/html" href="https://shkspr.mobi/blog/2024/02/minority-governments-and-the-boundary-commission/" />

		<id>https://shkspr.mobi/blog/?p=49667</id>
		<updated>2024-02-20T20:21:11Z</updated>
		<published>2024-02-20T12:34:55Z</published>
		<category scheme="https://shkspr.mobi/blog" term="/etc/" /><category scheme="https://shkspr.mobi/blog" term="election" /><category scheme="https://shkspr.mobi/blog" term="general election" /><category scheme="https://shkspr.mobi/blog" term="politics" /><category scheme="https://shkspr.mobi/blog" term="uk" />
		<summary type="html"><![CDATA[The UK is almost certain to have a General Election this year1. The Boundary Commission for England2 has reworked the existing Parliamentary constituencies to make them more fair3. As such, constituencies are generally more equal in terms of electorate. But, of course, geography trumps geometry. So the Isle of Wight now has two constituencies of [&#8230;]]]></summary>

					<content type="html" xml:base="https://shkspr.mobi/blog/2024/02/minority-governments-and-the-boundary-commission/"><![CDATA[<p>The UK is almost certain to have a General Election this year<sup id="fnref-49667-elx"><a href="#fn-49667-elx" class="jetpack-footnote" title="Read footnote.">1</a></sup>. The Boundary Commission for England<sup id="fnref-49667-eng"><a href="#fn-49667-eng" class="jetpack-footnote" title="Read footnote.">2</a></sup> has <a href="https://boundarycommissionforengland.independent.gov.uk/data-and-resources/">reworked the existing Parliamentary constituencies</a> to make them more fair<sup id="fnref-49667-fair"><a href="#fn-49667-fair" class="jetpack-footnote" title="Read footnote.">3</a></sup>.</p>
<p>As such, constituencies are <em>generally</em> more equal in terms of electorate. But, of course, geography trumps geometry. So the Isle of Wight now has two constituencies of 56k and 54k, whereas the average constituency has 73k.</p>
<p>I wanted to know if these new boundaries meant that a political party could win the majority of votes, but still not get a majority of seats<sup id="fnref-49667-fptp"><a href="#fn-49667-fptp" class="jetpack-footnote" title="Read footnote.">4</a></sup>.  So I <a href="https://commonslibrary.parliament.uk/boundary-review-2023-which-seats-will-change/">downloaded the data</a>.</p>
<p>There are 650 seats in The UK. Obviously, if 649 of them had turnouts of 3 people - 2 voting for Party X and 1 for Party Y - and the last seat had 74k people vote for Y, then X wins with a minority of the national vote. But let's go for something more realistic.</p>
<p>The total electorate is 47,558,348<sup id="fnref-49667-ONS"><a href="#fn-49667-ONS" class="jetpack-footnote" title="Read footnote.">5</a></sup>. Therefore, a party would need 23,779,175 votes to win a national majority.</p>
<p>If Party X won 100% of the vote in the most populous 316 constituencies, they'd have 23,844,185 votes.<br />
If Party Y won 100% of the vote in the remaining 334 constituencies, they'd have 23,714,163 votes.</p>
<p>So, yes, it is <em>technically<sup id="fnref-49667-tech"><a href="#fn-49667-tech" class="jetpack-footnote" title="Read footnote.">6</a></sup></em> possible for a political party to win the majority of votes but still not win the majority of seats. In fact, a party could win 24,322,616 votes (51.1%) and still be one seat short of a plurality.</p>
<h2 id='but-what-about'><a href='#but-what-about' class='heading-link'>But what about…?</a></h2>
<p>Is this <em>probable</em>? No. Even in the wildest fantasies of party faithful, no one is winning 100% of the vote in any constituency. England, Scotland, Wales, and NI each have their own political parties and vastly different electorate.</p>
<p>But is it <em>possible</em>? Yes. If Party X won the 326 least-populous seats with 100% of the vote, they would have a majority in Parliament yet only have 48.6% of the popular vote.</p>
<div class="footnotes">
<hr />
<ol>
<li id="fn-49667-elx">
Personally, I think Rishi will hold it in January 2025. Clinging on to power until the very last second, hoping something will happen that will change his fortune.&#160;<a href="#fnref-49667-elx" title="Return to main content.">&#8617;</a>
</li>
<li id="fn-49667-eng">
The UK is a country of four countries. Yes, it is complicated. No, I won't explain it.&#160;<a href="#fnref-49667-eng" title="Return to main content.">&#8617;</a>
</li>
<li id="fn-49667-fair">
Yes, I know your favourite MP has been done dirty by these changes. No, I don't think there are political shenanigans afoot targeting specific MPs.&#160;<a href="#fnref-49667-fair" title="Return to main content.">&#8617;</a>
</li>
<li id="fn-49667-fptp">
The UK's "First Past The Post" system means that the national vote share is often wildly different to the number of seats won. But I'm unaware of an election where a party won the most votes but didn't take the most seats.&#160;<a href="#fnref-49667-fptp" title="Return to main content.">&#8617;</a>
</li>
<li id="fn-49667-ONS">
The <a href="https://www.ons.gov.uk/peoplepopulationandcommunity/elections/electoralregistration/bulletins/electoralstatisticsforuk/december2021">Office for National Statistics</a> says "In December 2021, there were 46,560,452 Parliamentary electoral registrations" - but let's not quibble.&#160;<a href="#fnref-49667-ONS" title="Return to main content.">&#8617;</a>
</li>
<li id="fn-49667-tech">
The best kind of possible!&#160;<a href="#fnref-49667-tech" title="Return to main content.">&#8617;</a>
</li>
</ol>
</div>
]]></content>
		
					<link rel="replies" type="text/html" href="https://shkspr.mobi/blog/2024/02/minority-governments-and-the-boundary-commission/#comments" thr:count="7" />
			<link rel="replies" type="application/atom+xml" href="https://shkspr.mobi/blog/2024/02/minority-governments-and-the-boundary-commission/feed/atom/" thr:count="7" />
			<thr:total>7</thr:total>
			</entry>
		<entry>
		<author>
			<name>@edent</name>
					</author>

		<title type="html"><![CDATA[Drinking Champagne with the Secretary of State]]></title>
		<link rel="alternate" type="text/html" href="https://shkspr.mobi/blog/2024/02/drinking-champagne-with-the-secretary-of-state/" />

		<id>https://shkspr.mobi/blog/?p=31509</id>
		<updated>2024-02-18T18:17:21Z</updated>
		<published>2024-02-19T12:34:34Z</published>
		<category scheme="https://shkspr.mobi/blog" term="/etc/" /><category scheme="https://shkspr.mobi/blog" term="dhsc" /><category scheme="https://shkspr.mobi/blog" term="meta" /><category scheme="https://shkspr.mobi/blog" term="nhsx" /><category scheme="https://shkspr.mobi/blog" term="retropost" /><category scheme="https://shkspr.mobi/blog" term="work" />
		<summary type="html"><![CDATA[This is a retropost. Written contemporaneously in February 2019, but published much later. My life is weird. Again. Looking out over London from the top floor. The Eye is glittering and the Palace of Westminster is glowing. Someone pours me a glass of (very expensive1) champagne, as the Secretary of State laughs at my witty [&#8230;]]]></summary>

					<content type="html" xml:base="https://shkspr.mobi/blog/2024/02/drinking-champagne-with-the-secretary-of-state/"><![CDATA[<p><ins datetime="2019-02-19T20:54:04+00:00">This is a <a href="https://shkspr.mobi/blog/tag/retropost/">retropost</a>. Written contemporaneously in February 2019, but published much later.</ins></p>
<p>My life is weird. Again.</p>
<p>Looking out over London from the top floor. The Eye is glittering and the Palace of Westminster is glowing.</p>
<p>Someone pours me a glass of (very expensive<sup id="fnref-31509-cham"><a href="#fn-31509-cham" class="jetpack-footnote" title="Read footnote.">1</a></sup>) champagne, as the Secretary of State laughs at my witty <em>bon mot</em>.</p>
<p>Is this my life now? People of distinction and influence listening to what I have to say? It isn't an oak-panelled room, with deep armchairs, where cigar-smoking men carve up the world. It's a modest and plain office where men (and women!) have gathered for a bit of mutual backslapping.  But I am here.  I'm in the room and being thanked.</p>
<p>And why not! We've all worked hard on <a href="https://www.gov.uk/government/news/nhsx-new-joint-organisation-for-digital-data-and-technology">launching NHSX</a> and are rewarded with a little audience. The chit-chat is awkward - despite the geniality, we're all aware that the boss is here.</p>
<p>Naturally, I believe someone is going to tap me on the shoulder and ask me what the hell I think I'm doing in a room full of proper grown-ups.  But, no, people keep asking me questions and telling me their well-practiced anecdotes.</p>
<p>It is simultaneously amazing and banal. I've been at this work-party several times in my career, with dozens of companies, with a parade of CEOs.  This feels different.  A tiny glimmer of "I've made <s>it</s> a difference!"</p>
<p>I eat my fill of crisps - I am driving later - and slip out. I want to savour the moment, but know too well the perils of outstaying my welcome. I float all the way home.</p>
<p>Proximity to power is a powerful glamour. I understand why some are drawn to it, and some are seemingly addicted.</p>
<p>But I'll be different, I'm sure, as I bask in the experience.</p>
<div class="footnotes">
<hr />
<ol>
<li id="fn-31509-cham">
The fizz has come from someone's home. No taxpayers' cash was splashed on booze.&#160;<a href="#fnref-31509-cham" title="Return to main content.">&#8617;</a>
</li>
</ol>
</div>
]]></content>
		
					<link rel="replies" type="text/html" href="https://shkspr.mobi/blog/2024/02/drinking-champagne-with-the-secretary-of-state/#comments" thr:count="0" />
			<link rel="replies" type="application/atom+xml" href="https://shkspr.mobi/blog/2024/02/drinking-champagne-with-the-secretary-of-state/feed/atom/" thr:count="0" />
			<thr:total>0</thr:total>
			</entry>
		<entry>
		<author>
			<name>@edent</name>
					</author>

		<title type="html"><![CDATA[ActivityPub Server in a Single PHP File]]></title>
		<link rel="alternate" type="text/html" href="https://shkspr.mobi/blog/2024/02/activitypub-server-in-a-single-file/" />

		<id>https://shkspr.mobi/blog/?p=49641</id>
		<updated>2024-02-18T11:33:01Z</updated>
		<published>2024-02-18T12:34:48Z</published>
		<category scheme="https://shkspr.mobi/blog" term="/etc/" /><category scheme="https://shkspr.mobi/blog" term="ActivityPub" /><category scheme="https://shkspr.mobi/blog" term="mastodon" /><category scheme="https://shkspr.mobi/blog" term="php" />
		<summary type="html"><![CDATA[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 [&#8230;]]]></summary>

					<content type="html" xml:base="https://shkspr.mobi/blog/2024/02/activitypub-server-in-a-single-file/"><![CDATA[<p>Any computer program can be designed to run from a single file if you architect it wrong enough!</p>
<p>I wanted to create the simplest possible Fediverse server which can be used as an educational tool to show how ActivityPub / Mastodon works.</p>
<p>The design goals were:</p>
<ul>
<li>Upload a single PHP file to the server.</li>
<li>No databases or separate config files.</li>
<li>Single Actor (i.e. not multi-user).</li>
<li>Allow the Actor to be followed.</li>
<li>Post plain-text messages to followers.</li>
<li>Be <em>roughly</em> standards compliant.</li>
</ul>
<p>And those goals have all been met! <a href="https://gitlab.com/edent/activitypub-single-php-file/">Check it out on GitLab</a>. I warn you though, it is the <em>nadir</em> of bad coding. There are no tests, bugger-all security, scalability isn't considered, and it is a mess. But it <em>works</em>.</p>
<p>You can follow the test user <code>@example@example.viii.fi</code></p>
<h2 id='architecture'><a href='#architecture' class='heading-link'>Architecture</a></h2>
<p>Firstly, I've slightly cheated on my "single file" stipulation. There's an <code>.htaccess</code> file which turns <code>example.com/whatever</code> into <code>example.com/index.php?path=whatever</code></p>
<p>The <code>index.php</code> file then takes that path and does <em>stuff</em>. It also contains all the configuration variables which is <strong>very bad</strong> practice.</p>
<p>Rather than using a database, it saves files to disk.</p>
<p>Again, this is <strong>not suitable</strong> 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.</p>
<h2 id='walkthrough'><a href='#walkthrough' class='heading-link'>Walkthrough</a></h2>
<p>This is a quick ramble through the code. It is reasonably well documented, I hope.</p>
<h3 id='preamble'><a href='#preamble' class='heading-link'>Preamble</a></h3>
<p>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.</p>
<pre><code class="language-php">    //  Set up the Actor&#039;s information
    $username = rawurlencode(&quot;example&quot;);    //  Encoded as it is often used as part of a URl
    $realName = &quot;E. Xample. Jr.&quot;;
    $summary  = &quot;Some text about the user.&quot;;
    $server   = $_SERVER[&quot;SERVER_NAME&quot;];    //  Domain name this is hosted on

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

    //  Password for sending messages
    $password = &quot;P4ssW0rd&quot;;
</code></pre>
<h3 id='logging'><a href='#logging' class='heading-link'>Logging</a></h3>
<p>ActivityPub is a "chatty" protocol. This takes all the requests your server receives and saves them in <code>/logs/</code> as a datestamped text file.</p>
<pre><code class="language-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( &quot;php://input&quot; ), 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[&quot;type&quot;] ) ) {
        $type = &quot; &quot; . $body[&quot;type&quot;];
    } else {
        $type = &quot;&quot;;
    }

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

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

    file_put_contents( &quot;logs/{$filename}&quot;, 
        &quot;Headers:     \n$headers    \n\n&quot; .
        &quot;Body Data:   \n$bodyData   \n\n&quot; .
        &quot;POST Data:   \n$postData   \n\n&quot; .
        &quot;GET Data:    \n$getData    \n\n&quot; .
        &quot;Files Data:  \n$filesData  \n\n&quot; .
        &quot;Request Data:\n$requestData\n\n&quot; .
        &quot;Server Data: \n$serverData \n\n&quot;
    );
</code></pre>
<h3 id='routing'><a href='#routing' class='heading-link'>Routing</a></h3>
<p>The <code>.htaccess</code> changes <code>/whatever</code> to <code>/?path=whatever</code><br />
This runs the function of the path requested.</p>
<pre><code class="language-php">    !empty( $_GET[&quot;path&quot;] )  ? $path = $_GET[&quot;path&quot;] : die();
    switch ($path) {
        case &quot;.well-known/webfinger&quot;:
            webfinger();
        case rawurldecode( $username ):
            username();
        case &quot;following&quot;:
            following();
        case &quot;followers&quot;:
            followers();
        case &quot;inbox&quot;:
            inbox();
        case &quot;write&quot;:
            write();
        case &quot;send&quot;:
            send();
        default:
            die();
    }
</code></pre>
<h3 id='webfinger'><a href='#webfinger' class='heading-link'>WebFinger</a></h3>
<p>The <a href="https://docs.joinmastodon.org/spec/webfinger/">WebFinger Protocol</a> is used to identify accounts.<br />
It is requested with <code>example.com/.well-known/webfinger?resource=acct:username@example.com</code><br />
This server only has one user, so it ignores the query string and always returns the same details.</p>
<pre><code class="language-php">    function webfinger() {
        global $username, $server;

        $webfinger = array(
            &quot;subject&quot; =&gt; &quot;acct:{$username}@{$server}&quot;,
              &quot;links&quot; =&gt; array(
                array(
                     &quot;rel&quot; =&gt; &quot;self&quot;,
                    &quot;type&quot; =&gt; &quot;application/activity+json&quot;,
                    &quot;href&quot; =&gt; &quot;https://{$server}/{$username}&quot;
                )
            )
        );
        header( &quot;Content-Type: application/json&quot; );
        echo json_encode( $webfinger );
        die();
    }
</code></pre>
<h3 id='username'><a href='#username' class='heading-link'>Username</a></h3>
<p>Requesting <code>example.com/username</code> returns a JSON document with the user's information.</p>
<pre><code class="language-php">    function username() {
        global $username, $realName, $summary, $server, $key_public;

        $user = array(
            &quot;@context&quot; =&gt; [
                &quot;https://www.w3.org/ns/activitystreams&quot;,
                &quot;https://w3id.org/security/v1&quot;
            ],
                                   &quot;id&quot; =&gt; &quot;https://{$server}/{$username}&quot;,
                                 &quot;type&quot; =&gt; &quot;Person&quot;,
                            &quot;following&quot; =&gt; &quot;https://{$server}/following&quot;,
                            &quot;followers&quot; =&gt; &quot;https://{$server}/followers&quot;,
                                &quot;inbox&quot; =&gt; &quot;https://{$server}/inbox&quot;,
                    &quot;preferredUsername&quot; =&gt;  rawurldecode($username),
                                 &quot;name&quot; =&gt; &quot;{$realName}&quot;,
                              &quot;summary&quot; =&gt; &quot;{$summary}&quot;,
                                  &quot;url&quot; =&gt; &quot;https://{$server}&quot;,
            &quot;manuallyApprovesFollowers&quot; =&gt;  true,
                         &quot;discoverable&quot; =&gt;  true,
                            &quot;published&quot; =&gt; &quot;2024-02-12T11:51:00Z&quot;,
            &quot;icon&quot; =&gt; [
                     &quot;type&quot; =&gt; &quot;Image&quot;,
                &quot;mediaType&quot; =&gt; &quot;image/png&quot;,
                      &quot;url&quot; =&gt; &quot;https://{$server}/icon.png&quot;
            ],
            &quot;publicKey&quot; =&gt; [
                &quot;id&quot;           =&gt; &quot;https://{$server}/{$username}#main-key&quot;,
                &quot;owner&quot;        =&gt; &quot;https://{$server}/{$username}&quot;,
                &quot;publicKeyPem&quot; =&gt; $key_public
            ]
        );
        header( &quot;Content-Type: application/activity+json&quot; );
        echo json_encode( $user );
        die();
    }
</code></pre>
<h3 id='following-followers'><a href='#following-followers' class='heading-link'>Following &amp; Followers</a></h3>
<p>These JSON documents show how many users are following / followers-of this account.<br />
The information here is self-attested. So you can lie and use any number you want.</p>
<pre><code class="language-php">function following() {
        global $server;

        $following = array(
              &quot;@context&quot; =&gt; &quot;https://www.w3.org/ns/activitystreams&quot;,
                    &quot;id&quot; =&gt; &quot;https://{$server}/following&quot;,
                  &quot;type&quot; =&gt; &quot;Collection&quot;,
            &quot;totalItems&quot; =&gt; 0,
                 &quot;items&quot; =&gt; []
        );
        header( &quot;Content-Type: application/activity+json&quot; );
        echo json_encode( $following );
        die();
    }
    function followers() {
        global $server;
        $followers = array(
              &quot;@context&quot; =&gt; &quot;https://www.w3.org/ns/activitystreams&quot;,
                    &quot;id&quot; =&gt; &quot;https://{$server}/followers&quot;,
                  &quot;type&quot; =&gt; &quot;Collection&quot;,
            &quot;totalItems&quot; =&gt; 0,
                 &quot;items&quot; =&gt; []
        );
        header( &quot;Content-Type: application/activity+json&quot; );
        echo json_encode( $followers );
        die();
    }
</code></pre>
<h3 id='inbox'><a href='#inbox' class='heading-link'>Inbox</a></h3>
<p>The <code>/inbox</code> is the main server. It receives all requests. This server only responds to "Follow" requests.<br />
A remote server sends a follow request which is a JSON file saying who they are.<br />
This code does not cryptographically validate the headers of the received message.<br />
The name of the remote user's server is saved to a file so that future messages can be delivered to it.<br />
An accept request is cryptographically signed and POST'd back to the remote server.</p>
<pre><code class="language-php">    function inbox() {
        global $body, $server, $username, $key_private;

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

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

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

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

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

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

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

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

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

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

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

        //  POST the message and header to the requester&#039;s inbox
        $ch = curl_init( $remoteServerUrl );
        curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
        curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, &quot;POST&quot; );
        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( &quot;error.txt&quot;,  curl_error( $ch ) );
        }
        curl_close($ch);
        die();
    }
</code></pre>
<h3 id='uuid'><a href='#uuid' class='heading-link'>UUID</a></h3>
<p>Every message sent should have a unique ID.<br />
This can be anything you like. Some servers use a random number.<br />
I prefer a date-sortable string.</p>
<pre><code class="language-php">    function uuid() {
        return sprintf( &quot;%08x-%04x-%04x-%04x-%012x&quot;,
            time(),
            mt_rand(0, 0xffff),
            mt_rand(0, 0xffff),
            mt_rand(0, 0x3fff) | 0x8000,
            mt_rand(0, 0xffffffffffff)
        );
    }
</code></pre>
<h3 id='signing-headers'><a href='#signing-headers' class='heading-link'>Signing Headers</a></h3>
<p>Every message that your server sends needs to be cryptographically signed with your Private Key.<br />
This is a complicated process. Please read "<a href="https://blog.joinmastodon.org/2018/07/how-to-make-friends-and-verify-requests/">How to make friends and verify requests</a>" for more information.</p>
<pre><code class="language-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 = &quot;https://{$server}/{$username}#main-key&quot;;

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

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

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

        //  The signing function returns the variable $signature
        //  https://www.php.net/manual/en/function.openssl-sign.php
        openssl_sign(
            $stringToSign, 
            $signature, 
            $signer, 
            OPENSSL_ALGO_SHA256
        );
        //  Encode the signature
        $signature_b64 = base64_encode( $signature );

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

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

        return $headers;
    }
</code></pre>
<h3 id='user-interface-for-writing'><a href='#user-interface-for-writing' class='heading-link'>User Interface for Writing</a></h3>
<p>This creates a basic HTML form. Type in your message and your password. It then POSTs the data to the <code>/send</code> endpoint.</p>
<pre><code class="language-php">    function write() {
        //  Display an HTML form for the user to enter a message.
echo &lt;&lt;&lt; HTML
&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en-GB&quot;&gt;
    &lt;head&gt;
        &lt;meta charset=&quot;UTF-8&quot;&gt;
        &lt;title&gt;Send Message&lt;/title&gt;
        &lt;style&gt;
            *{font-family:sans-serif;font-size:1.1em;}
        &lt;/style&gt;
    &lt;/head&gt;
    &lt;body&gt;
        &lt;form action=&quot;/send&quot; method=&quot;post&quot; enctype=&quot;multipart/form-data&quot;&gt;
            &lt;label   for=&quot;content&quot;&gt;Your message:&lt;/label&gt;&lt;br&gt;
            &lt;textarea id=&quot;content&quot; name=&quot;content&quot; rows=&quot;5&quot; cols=&quot;32&quot;&gt;&lt;/textarea&gt;&lt;br&gt;
            &lt;label   for=&quot;password&quot;&gt;Password&lt;/label&gt;&lt;br&gt;
            &lt;input  type=&quot;password&quot; name=&quot;password&quot; id=&quot;password&quot; size=&quot;32&quot;&gt;&lt;br&gt;
            &lt;input  type=&quot;submit&quot;  value=&quot;Post Message&quot;&gt; 
        &lt;/form&gt;
    &lt;/body&gt;
&lt;/html&gt;
HTML;
        die();
    }
</code></pre>
<h3 id='send-endpoint'><a href='#send-endpoint' class='heading-link'>Send Endpoint</a></h3>
<p>This takes the submitted message and checks the password is correct.<br />
It reads the <code>followers.json</code> file and sends the message to every server that is following this account.</p>
<pre><code class="language-php">    function send() {
        global $password, $server, $username, $key_private;

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

        //  Get the posted content
        $content = $_POST[&quot;content&quot;];

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

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

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

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

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

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

        //  Read existing users and get their hosts
        $followers_file = file_get_contents( &quot;followers.json&quot; );
        $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 = &quot;/inbox&quot;;

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

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

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

            curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
            curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, &quot;POST&quot; );
            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 &amp;&amp; $status == CURLM_OK );

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

        //  Render the JSON so the user can see the POST has worked
        header( &quot;Location: https://{$server}/posts/{$guid}.json&quot; );
        die();
    }
</code></pre>
<h2 id='next-steps'><a href='#next-steps' class='heading-link'>Next Steps</a></h2>
<p>This is <em>not</em> intended to be used in production. <strong>Ever</strong>.  But if you would like to contribute more simple examples of how the protocol works, please <a href="https://gitlab.com/edent/activitypub-single-php-file/">come and play on GitLab</a>.</p>
<p>You can follow the test user <code>@example@example.viii.fi</code></p>
]]></content>
		
					<link rel="replies" type="text/html" href="https://shkspr.mobi/blog/2024/02/activitypub-server-in-a-single-file/#comments" thr:count="7" />
			<link rel="replies" type="application/atom+xml" href="https://shkspr.mobi/blog/2024/02/activitypub-server-in-a-single-file/feed/atom/" thr:count="7" />
			<thr:total>7</thr:total>
			</entry>
		<entry>
		<author>
			<name>@edent</name>
					</author>

		<title type="html"><![CDATA[Internationalise The Fediverse]]></title>
		<link rel="alternate" type="text/html" href="https://shkspr.mobi/blog/2024/02/internationalise-the-fediverse/" />

		<id>https://shkspr.mobi/blog/?p=49643</id>
		<updated>2024-02-16T11:48:17Z</updated>
		<published>2024-02-17T12:34:58Z</published>
		<category scheme="https://shkspr.mobi/blog" term="/etc/" /><category scheme="https://shkspr.mobi/blog" term="ActivityPub" /><category scheme="https://shkspr.mobi/blog" term="fediverse" /><category scheme="https://shkspr.mobi/blog" term="i18n" /><category scheme="https://shkspr.mobi/blog" term="mastodon" /><category scheme="https://shkspr.mobi/blog" term="unicode" />
		<summary type="html"><![CDATA[We live in the future now. It is OK to use Unicode everywhere. It seems bizarre to me that modern Internet services sometimes "forget" that there's a world outside the Anglosphere. Some people have the temerity to speak foreign languages! And some of those languages have accents on their letters!! Even worse, some don't use [&#8230;]]]></summary>

					<content type="html" xml:base="https://shkspr.mobi/blog/2024/02/internationalise-the-fediverse/"><![CDATA[<p>We live in the future now. It is OK to use Unicode everywhere.</p>
<p>It seems bizarre to me that modern Internet services sometimes "forget" that there's a world outside the Anglosphere. Some people have the temerity to speak <em>foreign</em> languages! And some of those languages have accents on their letters!! Even worse, some don't use English letters <em>at all!!!</em></p>
<p>A decade ago, I was miffed that <a href="https://shkspr.mobi/blog/2013/06/is-github-racist/">GitHub only supported some ASCII characters</a> in its project names. There's no <em>technical</em> reason why your repo can't be called "ഹലോ വേൾഡ്".</p>
<p>Similarly, I'm frustrated that Mastodon (the largest ActivityPub service) <a href="https://github.com/mastodon/mastodon/issues/8417">doesn't allow Unicode usernames</a> and has <a href="https://jam.xwx.moe/notice/AdXsJF6Q5oYHJBEAiG">resisted efforts to change</a>.</p>
<p>So I built a small ActivityPub server which publishes content from an Actor called <a href="https://i18n.viii.fi/.well-known/webfinger"><code>@你好@i18n.viii.fi</code></a> - it is only a demo account, but it works!</p>
<p>Some ActivityPub clients report that they are able to follow it and receive messages from it. Others - like Mastodon - simply can't see anything from it.  Take a look <a href="https://mastodon.social/@Edent/111920759100955860">at the replies on Mastodon</a> to see which services work.  You can also <a href="https://fed.xnor.in/users/$Aet3ViWYORXdinGChM">see some of its posts on the Fediverse</a>.</p>
<h2 id='what-does-the-fox-spec-say'><a href='#what-does-the-fox-spec-say' class='heading-link'>What Does The <del>Fox</del> Spec Say?</a></h2>
<p>The ActivityPub specification says:</p>
<blockquote><p>
  Building an international base of users is important in a federated network.<br />
  <a href="https://www.w3.org/TR/activitypub/#i18n-concerns">Internationalization</a>
</p></blockquote>
<p>I can't find anything in the specifications which limits what languages a username can be written in. But there are a few clues scattered about.</p>
<p>The user's <code>@</code> name is defined by <code>preferredUsername</code> which is:</p>
<blockquote><p>
  A short username which may be used to refer to the actor, with no uniqueness guarantees.<br />
  <a href="https://www.w3.org/TR/activitypub/#preferredUsername">4.1 Actor objects</a>
</p></blockquote>
<p>There's nothing in there about what scripts it can contain. However, later on, the spec says:</p>
<blockquote><p>
  Properties containing natural language values, such as <code>name</code>, <code>preferredUsername</code>, or <code>summary</code>, make use of <a href="https://www.w3.org/TR/activitystreams-core/#naturalLanguageValues">natural language support defined in ActivityStreams</a>.<br />
  <a href="https://www.w3.org/TR/activitypub/#h-note-2">4. Actors</a>
</p></blockquote>
<p>So it is expected that a preferred username could be written in multiple scripts. Which implies that the default need not be limited to A-Z0-9.</p>
<p>The <a href="https://www.w3.org/TR/activitystreams-core/#marking-up-language">ActivityStreams specification talks about language mapping</a>.</p>
<p>Finally, the <a href="https://www.w3.org/TR/activitypub/#liked-property">ActivityPub specification has some examples on non-Latin text</a> in names.</p>
<p>So, I think that it is acceptable for usernames to be written in a variety of non-Latin scripts.</p>
<h2 id='but-what-about'><a href='#but-what-about' class='heading-link'>But What About...?</a></h2>
<p>There are usually a few objections to "Unicode Everywhere" zealots like me. I'd like to forestall any arguments.</p>
<h3 id='what-about-homograph-attacks'><a href='#what-about-homograph-attacks' class='heading-link'>What about homograph attacks?</a></h3>
<p>Well, what about them? ASCII has plenty of similar looking characters. I doubt most people would notice when a capital i is replaced by a lower L - and vice-versa. Similarly the kerning issue of an r and n looking like an m is well known. Are mixed language homographs more dangerous? I don't think so.</p>
<h3 id='what-if-people-make-names-that-cant-be-typed'><a href='#what-if-people-make-names-that-cant-be-typed' class='heading-link'>What if people make names that can't be typed?</a></h3>
<p>Well, what if they do? Maybe not being found by people who can't type your language is a feature, not a bug.  But, anyway, clients can let users search for other people, or copy and paste their names.</p>
<h3 id='what-about-weird-zalgo-text'><a href='#what-about-weird-zalgo-text' class='heading-link'>What about weird "Zalgo" text?</a></h3>
<p>It is up to a client to decide how they want to render text input. The "problems" of strange Unicode combinations are well known. This is not a hard computer-science problem.</p>
<h3 id='what-about-bi-directional-text'><a href='#what-about-bi-directional-text' class='heading-link'>What about bi-directional text?</a></h3>
<p><a href="https://www.w3.org/TR/activitystreams-core/#h-biditext">The spec makes clear this is allowed</a>.</p>
<h3 id='do-people-even-want-a-username-in-their-own-script'><a href='#do-people-even-want-a-username-in-their-own-script' class='heading-link'>Do people even want a username in their own script?</a></h3>
<p>I have no evidence for this. But I bet you'd get pretty frustrated if you had to switch keyboard just to type your own name, wouldn't you? In any case, why can't I have a username of <code>@😉</code></p>
<h2 id='whats-next'><a href='#whats-next' class='heading-link'>What's Next?</a></h2>
<p>If you build ActivityPub software, give some thought to the billions of people who don't have names which easily fit into ASCII.</p>
<p>If your software can see <a href="https://i18n.viii.fi/.well-known/webfinger"><code>@你好@i18n.viii.fi</code></a> and its posts, please let me know.</p>
]]></content>
		
					<link rel="replies" type="text/html" href="https://shkspr.mobi/blog/2024/02/internationalise-the-fediverse/#comments" thr:count="37" />
			<link rel="replies" type="application/atom+xml" href="https://shkspr.mobi/blog/2024/02/internationalise-the-fediverse/feed/atom/" thr:count="37" />
			<thr:total>37</thr:total>
			</entry>
		<entry>
		<author>
			<name>@edent</name>
					</author>

		<title type="html"><![CDATA[Are we &#039;appy about change?]]></title>
		<link rel="alternate" type="text/html" href="https://shkspr.mobi/blog/2024/02/are-we-appy-about-change/" />

		<id>https://shkspr.mobi/blog/?p=49587</id>
		<updated>2024-02-14T20:38:50Z</updated>
		<published>2024-02-16T12:34:05Z</published>
		<category scheme="https://shkspr.mobi/blog" term="/etc/" /><category scheme="https://shkspr.mobi/blog" term="Apps" /><category scheme="https://shkspr.mobi/blog" term="gov.uk" />
		<summary type="html"><![CDATA[Shortly before I left the Civil Service in 2023, I made a complete fool of myself. Someone on Slack was discussing their department's app and I (rather snidely) asked why it was an app rather than a website. After all, one of the seminal blog posts of GDS was about not building apps. In response, [&#8230;]]]></summary>

					<content type="html" xml:base="https://shkspr.mobi/blog/2024/02/are-we-appy-about-change/"><![CDATA[<p>Shortly before I left the Civil Service in 2023, I made a complete fool of myself. Someone on Slack was discussing their department's app and I (rather snidely) asked why it was an app rather than a website. After all, one of the <a href="https://gds.blog.gov.uk/2013/03/12/were-not-appy-not-appy-at-all/">seminal blog posts of GDS was about <em>not</em> building apps</a>.</p>
<p>In response, I was given an eye-roll and told "because that's how most people get their information, <em>grandpa!</em>"<sup id="fnref-49587-gp"><a href="#fn-49587-gp" class="jetpack-footnote" title="Read footnote.">1</a></sup></p>
<p>Last week, I saw this job advert and I got an involuntary shudder.</p>
<p><img fetchpriority="high" decoding="async" src="https://shkspr.mobi/blog/wp-content/uploads/2024/02/govuk-app-fs8.png" alt="Advert which says &quot;Fancy working with us on the first GOV.UK mobile app? These Android developer roles are exciting...&quot;" width="720" height="707" class="aligncenter size-full wp-image-49627" /></p>
<p>But I am wrong. Time moves on. Some of us find that difficult to cope with. The world is different and that difference is to be embraced.</p>
<p>Let's take a look at what people were saying about mobile apps in government a decade ago:</p>
<blockquote><p>
  government’s position is that native and hybrid apps are rarely justified - make sure your service meets the Digital by Default Service Standard and it will work well on mobile devices (responsive HTML5)<br />
  <a href="https://gds.blog.gov.uk/2013/03/12/were-not-appy-not-appy-at-all/">"We're not ‘appy. Not ‘appy at all."</a> (2013)
</p></blockquote>
<p>It wasn't a <em>ban</em> on apps, it was merely saying "if you can't build a decent website, then you're probably not competent enough to build a decent app."<sup id="fnref-49587-build"><a href="#fn-49587-build" class="jetpack-footnote" title="Read footnote.">2</a></sup></p>
<p>I came to GDS directly from a decade working in the mobile industry. I'd gone from dumbphones, to BlackBerrys, to the explosion of smartphones. Back in 2013, it wasn't immediately obvious who would win the smartphone wars<sup id="fnref-49587-obvs"><a href="#fn-49587-obvs" class="jetpack-footnote" title="Read footnote.">3</a></sup>. The iPhone app store was only 5 years old. <a href="https://shkspr.mobi/blog/tag/wp7/">Windows Phone 7</a> was being heavily pushed by Microsoft. <a href="https://shkspr.mobi/blog/2012/06/how-do-you-solve-a-problem-like-blackberry/">BlackBerry 10</a> was launching to great fanfare. Symbian was probably dead, but <a href="https://shkspr.mobi/blog/tag/limo/">LiMo</a> and <a href="https://shkspr.mobi/blog/2010/03/choosing-a-new-phone/">Maemo</a> might have had a comeback. Android was a huge fragmented mess. HP was determined to relaunch its fortunes with <a href="https://en.wikipedia.org/wiki/WebOS">WebOS</a> while Mozilla were going after the lower-end handsets with <a href="https://en.wikipedia.org/wiki/Firefox_OS">Firefox OS</a>.</p>
<p>Government services have to be accessible to everyone.  Would departments <em>really</em> have produced apps for half-a-dozen different operating systems? Would they have had the skill and budget to keep them all updated?</p>
<p>Government services shouldn't disturb the market. If the UK had said "Right! You can only submit a tax return using a BlackBerry!" would that have unfairly caused a spike in their market share?</p>
<p>Even still, <a href="https://web.archive.org/web/20151108105101/http://think.withgoogle.com/mobileplanet/en/">smartphone penetration was only at about 60% in the UK</a>.  Did it make sense to spend huge amounts of money for something which wasn't universally accessible?</p>
<p>Back then, a de-facto ban on apps was a sensible precaution.</p>
<p>But today?</p>
<p>I was involved in the <a href="https://shkspr.mobi/blog/2023/04/so-farewell-then-covid-19-app/">UK's COVID-19 App</a>. By that time, there were really only two smartphone OSes in the game; Android and iOS<sup id="fnref-49587-linux"><a href="#fn-49587-linux" class="jetpack-footnote" title="Read footnote.">4</a></sup>.  The APIs had stabilised such that developing a single app per platform was feasible<sup id="fnref-49587-testing"><a href="#fn-49587-testing" class="jetpack-footnote" title="Read footnote.">5</a></sup>.</p>
<p>There are also things which the Web just can't do. Apps are needed to read the NFC chips in passports, to use BLE for contact tracing, and to enforce biometric security on accounts.</p>
<p>That contact tracing app, for better or worse, helped show that it was possible for Government to develop national-level apps and that people would install and use them.</p>
<p>Does the world need a "GOV.UK App"? I don't think so. But I'm old and wrong<sup id="fnref-49587-old"><a href="#fn-49587-old" class="jetpack-footnote" title="Read footnote.">6</a></sup>. Research shows that people trust apps more than the web. Lower-income households are more likely to have a shared smartphone than a PC - and an app with multiple accounts is more secure. The web still isn't great at caching data for offline use - so being able to look stuff up when you're out of signal is a must. Apps usually use less data than websites - which is great for people with limited data allowances, or on slow speeds.</p>
<p>Some techies think that we are Keepers of The Sacred Flame.  If we rant hard enough, progress will stop and we'll be comfortable that our knowledge isn't obsolete.  I think I'm rather happy to be freed of that notion.</p>
<p><i lang="la">Tempus fugit, tu senex fossilium. Esne laetus?</i></p>
<div class="footnotes">
<hr />
<ol>
<li id="fn-49587-gp">
They didn't actually eye-roll and "grandpa" me, of course. They were perfectly polite. But I sure felt that subtext!&#160;<a href="#fnref-49587-gp" title="Return to main content.">&#8617;</a>
</li>
<li id="fn-49587-build">
Again, implied in subtext.&#160;<a href="#fnref-49587-build" title="Return to main content.">&#8617;</a>
</li>
<li id="fn-49587-obvs">
I'm sure <em>you</em> found it obvious. But most people were sensible and hedged their bets.&#160;<a href="#fnref-49587-obvs" title="Return to main content.">&#8617;</a>
</li>
<li id="fn-49587-linux">
Yes, I know you run some weird custom Linux on your phone and are happy recompiling every time there's an update. But you aren't even a statistical blip.&#160;<a href="#fnref-49587-linux" title="Return to main content.">&#8617;</a>
</li>
<li id="fn-49587-testing">
Of course, testing on dozens of different phones with varying ROMs is still expensive and time-consuming.&#160;<a href="#fnref-49587-testing" title="Return to main content.">&#8617;</a>
</li>
<li id="fn-49587-old">
It is rather liberating knowing that many of the truths we cling to depend greatly on our own point of view.&#160;<a href="#fnref-49587-old" title="Return to main content.">&#8617;</a>
</li>
</ol>
</div>
]]></content>
		
					<link rel="replies" type="text/html" href="https://shkspr.mobi/blog/2024/02/are-we-appy-about-change/#comments" thr:count="14" />
			<link rel="replies" type="application/atom+xml" href="https://shkspr.mobi/blog/2024/02/are-we-appy-about-change/feed/atom/" thr:count="14" />
			<thr:total>14</thr:total>
			</entry>
		<entry>
		<author>
			<name>@edent</name>
					</author>

		<title type="html"><![CDATA[&quot;I&#039;m sorry, Dave. I&#039;m afraid that computation is too carbon intensive.&quot;]]></title>
		<link rel="alternate" type="text/html" href="https://shkspr.mobi/blog/2024/02/im-sorry-dave-im-afraid-that-computation-is-too-carbon-intensive/" />

		<id>https://shkspr.mobi/blog/?p=49624</id>
		<updated>2024-02-14T16:59:21Z</updated>
		<published>2024-02-15T12:34:47Z</published>
		<category scheme="https://shkspr.mobi/blog" term="/etc/" /><category scheme="https://shkspr.mobi/blog" term="Climate Crisis" /><category scheme="https://shkspr.mobi/blog" term="electricity" /><category scheme="https://shkspr.mobi/blog" term="near future" />
		<summary type="html"><![CDATA[An interesting snippet about the future of computation: Starting with this build, we are introducing the Power Grid Forecast API. This API empowers app developers to optimize app behavior, minimizing environmental impact by shifting background tasks to times when more renewable energy is available in the local electrical grid. Announcing Windows 11 Insider Preview Build [&#8230;]]]></summary>

					<content type="html" xml:base="https://shkspr.mobi/blog/2024/02/im-sorry-dave-im-afraid-that-computation-is-too-carbon-intensive/"><![CDATA[<p>An interesting snippet about the future of computation:</p>
<blockquote><p>
  Starting with this build, we are introducing the Power Grid Forecast API. This API empowers app developers to optimize app behavior, minimizing environmental impact by shifting background tasks to times when more renewable energy is available in the local electrical grid.<br />
  <a href="https://blogs.windows.com/windows-insider/2024/02/08/announcing-windows-11-insider-preview-build-26052-canary-and-dev-channels/">Announcing Windows 11 Insider Preview Build 26052</a>
</p></blockquote>
<p>Some computational processes take a lot of electricity. Back in the old days, batch computing meant that programmers could use "spare" CPU cycles at night. Their code ran when no-one else was using the machine. These days, it is common for computational tasks to be outsourced to where-ever electricity is least polluting. For example, shifting overnight processing to countries on the other side of the planet with excess solar power.  With Windows' new APIs, they can wait until <a href="https://shkspr.mobi/blog/2023/12/electricity-thats-too-cheap-to-meter/">electricity is too cheap to meter</a> before doing something computationally expensive.</p>
<p>Computing budgets are usually set in terms of FLOPS, Watts, or seconds. I think it is fascinating that we might soon routinely add CO<sub>2</sub> to that equation.</p>
<p>I've written before about <a href="https://shkspr.mobi/blog/2017/11/what-if-your-internet-connected-fridge-came-with-free-electricity/">domestic appliances being smart about their electricity use</a>. It would be brilliant if your freezer knew to wait 10 minutes for less-polluting power before running its compressor. Similarly, you could tell a clothes dryer to be finished by the time you get home - but let it decide when to actually run.</p>
<p>I also wonder if a games console could drop its FPS, or outsource some of its processing to the cloud, when domestic electricity becomes too expensive.</p>
<p>Of course, the downside is obvious. Can your code refuse to run if it thinks it will cause harm? Is there an interpretation of the <a href="https://shkspr.mobi/blog/2019/01/i-robot-the-3-laws-considered-harmful/">laws of robotics</a> which prevents a machine from polluting?</p>
<p>I don't really think that domestic devices will refuse our requests in the near future. But I am curious what incentives - financial or otherwise - there might be to encourage more efficient resource use.</p>
]]></content>
		
					<link rel="replies" type="text/html" href="https://shkspr.mobi/blog/2024/02/im-sorry-dave-im-afraid-that-computation-is-too-carbon-intensive/#comments" thr:count="3" />
			<link rel="replies" type="application/atom+xml" href="https://shkspr.mobi/blog/2024/02/im-sorry-dave-im-afraid-that-computation-is-too-carbon-intensive/feed/atom/" thr:count="3" />
			<thr:total>3</thr:total>
			</entry>
		<entry>
		<author>
			<name>@edent</name>
					</author>

		<title type="html"><![CDATA[HOWTO: Sort BitWarden Passwords by Date]]></title>
		<link rel="alternate" type="text/html" href="https://shkspr.mobi/blog/2024/02/howto-sort-bitwarden-passwords-by-date/" />

		<id>https://shkspr.mobi/blog/?p=49616</id>
		<updated>2024-02-10T15:47:11Z</updated>
		<published>2024-02-14T12:34:30Z</published>
		<category scheme="https://shkspr.mobi/blog" term="/etc/" /><category scheme="https://shkspr.mobi/blog" term="bitwarden" /><category scheme="https://shkspr.mobi/blog" term="HowTo" /><category scheme="https://shkspr.mobi/blog" term="json" /><category scheme="https://shkspr.mobi/blog" term="linux" />
		<summary type="html"><![CDATA[I highly recommend BitWarden as a password manager. It is free, open source, and has a great range of apps and APIs. The one thing it doesn't have is a way to sort your accounts by creation date. I now have over a thousand accounts that I've added - so I wanted to prune away [&#8230;]]]></summary>

					<content type="html" xml:base="https://shkspr.mobi/blog/2024/02/howto-sort-bitwarden-passwords-by-date/"><![CDATA[<p>I highly recommend <a href="https://bitwarden.com">BitWarden</a> as a password manager. It is free, open source, and has a great range of apps and APIs.</p>
<p>The one thing it <a href="https://community.bitwarden.com/t/sort-items-by-date-of-modification-addition-last-use-etc/2484">doesn't have is a way to sort your accounts by creation date</a>.  I now have over a thousand accounts that I've added - so I wanted to prune away some of the older ones.</p>
<p>So, here's how to do it.</p>
<h2 id='export-your-vault'><a href='#export-your-vault' class='heading-link'>Export your vault</a></h2>
<p>In the <em>desktop</em> version of BitWarden, go to File → Export Vault.  Choose the JSON format (this doesn't work for CSV) and follow the on-screen instructions.</p>
<p><img decoding="async" src="https://shkspr.mobi/blog/wp-content/uploads/2024/02/BitWarden-Export-fs8.png" alt="Screenshot of the BitWarden export page." width="793" height="500" class="aligncenter size-full wp-image-49617" /></p>
<p><strong>NOTE!</strong> This will save <em>all</em> of your passwords to disk. Do not do this on a shared machine. Delete the file as soon as you are done with it.</p>
<h2 id='sort-with-jq'><a href='#sort-with-jq' class='heading-link'>Sort with JQ</a></h2>
<p>Using the <a href="https://jqlang.github.io/jq/"><code>jq</code> utility</a> it is possible to search and sort the exported JSON.</p>
<p>This command pipes your export to JQ. It selects all the <code>items</code>, then it sorts by when the item was last edited. It then displays the name of the account and the date, followed by a newline.</p>
<pre><code>cat bitwarden.json | jq -r &#039;.items | sort_by(.revisionDate) | .[] | .name, .revisionDate, &quot; &quot; &#039;
</code></pre>
<h2 id='purge'><a href='#purge' class='heading-link'>Purge!</a></h2>
<p>You <em>could</em> <a href="https://bitwarden.com/help/cli/#delete">use the API to delete items</a> based on their ID.  Personally, I'd rather go through manually.</p>
<h2 id='whats-next'><a href='#whats-next' class='heading-link'>What's next?</a></h2>
<p>It would be great if BitWarden allowed sorting by date in their UI. Even better if they could sort by usage.  Until then, I'll spend every Valentine's Day manually deleting old and unloved accounts.</p>
]]></content>
		
					<link rel="replies" type="text/html" href="https://shkspr.mobi/blog/2024/02/howto-sort-bitwarden-passwords-by-date/#comments" thr:count="3" />
			<link rel="replies" type="application/atom+xml" href="https://shkspr.mobi/blog/2024/02/howto-sort-bitwarden-passwords-by-date/feed/atom/" thr:count="3" />
			<thr:total>3</thr:total>
			</entry>
		<entry>
		<author>
			<name>@edent</name>
					</author>

		<title type="html"><![CDATA[VR Game Review: Moss ★★★★☆]]></title>
		<link rel="alternate" type="text/html" href="https://shkspr.mobi/blog/2024/02/vr-game-review-moss/" />

		<id>https://shkspr.mobi/blog/?p=49619</id>
		<updated>2024-02-14T07:20:24Z</updated>
		<published>2024-02-13T12:34:14Z</published>
		<category scheme="https://shkspr.mobi/blog" term="/etc/" /><category scheme="https://shkspr.mobi/blog" term="Game Review" /><category scheme="https://shkspr.mobi/blog" term="oculus" /><category scheme="https://shkspr.mobi/blog" term="vr" />
		<summary type="html"><![CDATA[It is impossible to describe just how cute this game is. Most VR games take place at "human scale" - you play as a human inside a building, or other human-sized space. But Moss lets you play as a mouse named Quill with you (the player) towering over her. You are a human literally peering [&#8230;]]]></summary>

					<content type="html" xml:base="https://shkspr.mobi/blog/2024/02/vr-game-review-moss/"><![CDATA[<p>It is impossible to describe just how <em>cute</em> this game is. Most VR games take place at "human scale" - you play as a human inside a building, or other human-sized space. But Moss lets you play as a mouse named Quill with you (the player) towering over her. You are a human literally peering down into a mouse-sized kingdom. It is one of the most stunning uses of VR in a game that I've seen.</p>
<div style="width: 620px;" class="wp-video"><!--[if lt IE 9]><script>document.createElement('video');</script><![endif]-->
<video class="wp-video-shortcode" id="video-49619-1" width="620" height="349" preload="metadata" controls="controls"><source type="video/mp4" src="https://shkspr.mobi/blog/wp-content/uploads/2024/02/mossout.mp4?_=1" /><a href="https://shkspr.mobi/blog/wp-content/uploads/2024/02/mossout.mp4">https://shkspr.mobi/blog/wp-content/uploads/2024/02/mossout.mp4</a></video></div>
<p>It is like playing with a dolls house. And yet, the scale of it is <em>epic</em>.</p>
<p><img loading="lazy" decoding="async" src="https://shkspr.mobi/blog/wp-content/uploads/2024/02/moss-epic-fs8.png" alt="A large hall with squirrel statues." width="1024" height="1024" class="aligncenter size-full wp-image-49620" /></p>
<p>Vast castles, haunted forests, arid deserts - it is a wonderland for a platformer/puzzler.</p>
<p>The puzzles are mostly very good. Move the thing, jump on the whatsit, move another thing, back to the start etc. Most of them take advantage of the 3D environment which leads to some lovely designs. And, if you get stuck, the little mouse will mime a clue to you.  Get it right, and she'll demand a high-five. It is lovely.</p>
<p>Except for the combat.</p>
<p>Bugs come out to fight you. You hit them with your sword. Repeat. When the enemy can be manipulated to be part of the puzzle, they become interesting. Otherwise, it is just a slog. A couple of the bosses kept me in a death-loop until I'd hit them enough. It wasn't even an interesting "wait until they rear then hit the underbelly" fight. Just button mashing.</p>
<p>I fully agree with <a href="http://web.archive.org/web/20110321031657/http://www.killerbetties.com/killer_women_jennifer_hepler?page=0%2C3">Jennifer Hepler</a> and <a href="https://youtu.be/yKIiUsbOO24?t=88">Dara Ó Briain</a> that games need a "Skip Combat" button. I'm here for the story, the puzzles, and the graphics. Hitting monsters is <em>boring</em>.</p>
<p>The story is fun - and set up nicely for a sequel. There's a bit of replayability with a couple of collectables you can find.</p>
<p>Like all VR games, it is expensive for the length of gameplay. £15 for a few hours. You can get <a href="https://www.oculus.com/appreferrals/TerenceEden/1654565391314903/?utm_source=oculus&utm_location=2&utm_parent=frl&utm_medium=app_referral">25% off using my referral link</a></p>
]]></content>
		
					<link rel="replies" type="text/html" href="https://shkspr.mobi/blog/2024/02/vr-game-review-moss/#comments" thr:count="1" />
			<link rel="replies" type="application/atom+xml" href="https://shkspr.mobi/blog/2024/02/vr-game-review-moss/feed/atom/" thr:count="1" />
			<thr:total>1</thr:total>
			</entry>
		<entry>
		<author>
			<name>@edent</name>
					</author>

		<title type="html"><![CDATA[Book Review: Julia - Sandra Newman ★★★★★]]></title>
		<link rel="alternate" type="text/html" href="https://shkspr.mobi/blog/2024/02/book-review-julia-sandra-newman/" />

		<id>https://shkspr.mobi/blog/?p=49584</id>
		<updated>2024-02-08T10:11:21Z</updated>
		<published>2024-02-12T12:34:26Z</published>
		<category scheme="https://shkspr.mobi/blog" term="/etc/" /><category scheme="https://shkspr.mobi/blog" term="Book Review" />
		<summary type="html"><![CDATA[The central schtick of this book is a cliché brilliantly delivered. Take a side-character from a beloved book and retell the story through their eyes. I only have hazy memories of reading 1984 - where Julia is little more than a femme fatale. This book is an explicit and visceral journey through Julia's life in [&#8230;]]]></summary>

					<content type="html" xml:base="https://shkspr.mobi/blog/2024/02/book-review-julia-sandra-newman/"><![CDATA[<p><img decoding="async" src="https://shkspr.mobi/blog/wp-content/uploads/2024/02/Julia_APPROVED-781x1200-1.jpg" alt="Book cover. The word &quot;Julia&quot; is superimposed on the number &quot;1984&quot;." width="200" class="alignleft size-full wp-image-49585" />The central schtick of this book is a cliché brilliantly delivered. Take a side-character from a beloved book and retell the story through their eyes.</p>
<p>I only have hazy memories of reading 1984 - where Julia is little more than a <i lang="fr">femme fatale</i>. This book is an explicit and visceral journey through Julia's life in Airstrip One.  We see how, from her point of view, Winston Smith is little more than a pathetic dreamer. His childish fantasies of toppling Big Bother are the last gasp of a snivelling coward. His love nothing but selfishness.</p>
<p>It isn't a radical reworking of 1984 - the themes about totalitarianism are mostly the same - but it is a brilliantly fresh perspective. Feminism wasn't even a subtext in the original whereas this brings it right to the surface. The impact of IngSoc on the psychosexual wellbeing of its subjects is laid bare.</p>
<p>I think it can be enjoyed without having previously read 1984. The original is so influential that you're likely to have absorbed most of it through cultural osmosis.  It is a disturbing book - full of violence and pain - but I devoured it eagerly. The ending is far superior to the original.</p>
]]></content>
		
					<link rel="replies" type="text/html" href="https://shkspr.mobi/blog/2024/02/book-review-julia-sandra-newman/#comments" thr:count="0" />
			<link rel="replies" type="application/atom+xml" href="https://shkspr.mobi/blog/2024/02/book-review-julia-sandra-newman/feed/atom/" thr:count="0" />
			<thr:total>0</thr:total>
			</entry>
		<entry>
		<author>
			<name>@edent</name>
					</author>

		<title type="html"><![CDATA[There should only ever be one way to express yourself]]></title>
		<link rel="alternate" type="text/html" href="https://shkspr.mobi/blog/2024/02/there-should-only-ever-be-one-way-to-express-yourself/" />

		<id>https://shkspr.mobi/blog/?p=45224</id>
		<updated>2024-02-05T20:55:28Z</updated>
		<published>2024-02-11T12:34:39Z</published>
		<category scheme="https://shkspr.mobi/blog" term="/etc/" /><category scheme="https://shkspr.mobi/blog" term="Computer Science" /><category scheme="https://shkspr.mobi/blog" term="language" /><category scheme="https://shkspr.mobi/blog" term="programming" /><category scheme="https://shkspr.mobi/blog" term="python" />
		<summary type="html"><![CDATA[I've been thinking about programming languages and their design. In her book about the divergence of the English and American languages, Lynne Murphy asks this question: wouldn’t it be great if language were logical and maximally efficient? If sentences had only as many syllables as strictly needed? If each word had a single, unique meaning? [&#8230;]]]></summary>

					<content type="html" xml:base="https://shkspr.mobi/blog/2024/02/there-should-only-ever-be-one-way-to-express-yourself/"><![CDATA[<p>I've been thinking about programming languages and their design.</p>
<p>In her book about the divergence of the English and American languages, Lynne Murphy asks this question:</p>
<blockquote><p>
  wouldn’t it be great if language were logical and maximally efficient? If sentences had only as many syllables as strictly needed? If each word had a single, unique meaning? If there were no homophones, so we’d not be able to mix up dear and deer or two and too?
</p></blockquote>
<p>That got me thinking about the creativity which can be expressed in code - and whether its a good thing.</p>
<p>Let's take an incredibly simple and common operation - incrementing an integer variable by one.  How would you do that? You've probably see these variations:</p>
<pre><code>$i = $i + 1;
</code></pre>
<p>or</p>
<pre><code>$i = $i++;
</code></pre>
<p>or</p>
<pre><code>$i = 1 + $i;
</code></pre>
<p>or</p>
<pre><code>$i = int( float_adder( float($i), 1.00 ) );
</code></pre>
<p>or</p>
<pre><code>i1, i2 = i1^i2, (i1&amp;i2) &lt;&lt; 1 
</code></pre>
<p>I'm sure you can come up with a few more esoteric methods.</p>
<p>The Python programming language has a <a href="https://legacy.python.org/dev/peps/pep-0020/">list of aphorisms for good programming practice</a>. One of which is:</p>
<blockquote><p>
  There should be one-- and preferably only one --obvious way to do it.
</p></blockquote>
<p>Is that right? As described in <a href="https://blog.startifact.com/posts/older/what-is-pythonic.html">What is Pythonic?</a>, the Python language itself has multiple ways to accomplish one thing.</p>
<p>But, is it a good idea?</p>
<p>Back to Lynne Murphy again:</p>
<blockquote><p>
  No, absolutely not. No way. Quit even thinking that. What are you, some kind of philistine? If Shakespeare hadn’t played with the number of syllables in his sentences, he would not have been able to communicate in iambic pentameter.
</p></blockquote>
<p>Shakespeare wasn't writing Python though, was he?</p>
]]></content>
		
					<link rel="replies" type="text/html" href="https://shkspr.mobi/blog/2024/02/there-should-only-ever-be-one-way-to-express-yourself/#comments" thr:count="7" />
			<link rel="replies" type="application/atom+xml" href="https://shkspr.mobi/blog/2024/02/there-should-only-ever-be-one-way-to-express-yourself/feed/atom/" thr:count="7" />
			<thr:total>7</thr:total>
			</entry>
		<entry>
		<author>
			<name>@edent</name>
					</author>

		<title type="html"><![CDATA[Forget Technocrats - Let&#039;s Get Some Realitycrats]]></title>
		<link rel="alternate" type="text/html" href="https://shkspr.mobi/blog/2024/02/forget-technocrats-lets-get-some-realitycrats/" />

		<id>https://shkspr.mobi/blog/?p=49252</id>
		<updated>2024-02-10T12:03:28Z</updated>
		<published>2024-02-10T12:34:10Z</published>
		<category scheme="https://shkspr.mobi/blog" term="/etc/" /><category scheme="https://shkspr.mobi/blog" term="politics" />
		<summary type="html"><![CDATA[I don't really care about ideology and doctrine any more. I just care about what works. I'm going to take a few (somewhat controversial) subjects and explain what I mean. Fundamentally, I believe that all energy companies should be nationalised and there should be a single energy supplier. I don't want to pay a dozen [&#8230;]]]></summary>

					<content type="html" xml:base="https://shkspr.mobi/blog/2024/02/forget-technocrats-lets-get-some-realitycrats/"><![CDATA[<p>I don't really care about ideology and doctrine any more.  I just care about what works.</p>
<p>I'm going to take a few (somewhat controversial) subjects and explain what I mean.</p>
<p>Fundamentally, I believe that all energy companies should be nationalised and there should be a single energy supplier. I don't want to pay a dozen CEOs, a dozen finance teams, and for a dozen advertising campaigns. Privatisation has been a complete waste.</p>
<p>And yet...</p>
<p>When my previous energy company was being shit, I fired them. I moved to a competitor who had a UK-based call centre and would actually reply to emails. Later, I moved to an energy supplier who gave me API access to my account. I genuinely do not believe that British Gas would have introduced anything so innovative if left without competition.</p>
<p>I am fundamentally against privatisation on an ideological level. But I can't ignore that it is useful to be able move to a provider which better fits my needs. I like that I can choose to pay more for something which matters to me. And the same is true of broadband, parcel delivery, supermarkets, <a href="https://en.wikipedia.org/wiki/Pickfords#Privatisation">travel agents</a> and lots of other services.</p>
<p>This isn't universal. Water privatisation hasn't worked. Trains aren't well suited to competitive pressures. I don't think the NHS should be further privatised. But, in <em>some</em> cases, privatisation has worked and I would be a fool not to adjust my political biases to take account of reality.</p>
<p>Let's take another example.  Pretend, just for a moment, that you are anti-abortion. You believe that the ideal number of abortions in a country should be zero.  Forget your ideological reasons - which countries have the lowest abortion rates? Well, it turns out to be the ones with high levels of sex education, easy access to contraceptives, excellent pre-natal care, and strong parental leave policies.  And they all have legal access to abortion services.</p>
<p>If you truly want to reduce the number of abortions, there are a wide range of policies which actually work and don't involve demonising women and doctors.</p>
<p>On another topic. I fervently believe in the Year Of The Linux Desktop and I know in my heart-of-hearts that the UK Government and NHS would save billions if only they switched away from Google/Microsoft/Apple and embraced LibreOffice! But, realistically, the cost of retraining staff far exceeds any savings on licencing.  I look at other governments which have attempted a wholesale switch to a new technology and I can't really see any long-term benefits.</p>
<p>You think that benefits scroungers should be pursued relentlessly because they're draining the budget and causing a moral hazard. But, realistically, far more is lost to corporate tax fraud - every pound invested in tax investigations yields incredible returns.  We can probably both agree that the ideal number of fraudulent benefits cases is zero - but the evidence shows we'd lose less if we tackle bigger issues.</p>
<p>I'm all for Constitutional Reform. A written constitution would be much better than the ramshackle collection of "traditions" which make up the UK's elusive operating manual. Although I can't help noticing that the US constitution didn't do anything to prevent Trump's excesses, nor did it oust him, nor is it preventing him from his on-going coup.  Whereas the UK dumped two failing and dangerous Prime Minsters without much fuss.</p>
<p>You staunchly argue against the introduction of a minimum wage. You think it will depress the labour market, reduce salaries, and destroy collective bargaining. <a href="https://www.gov.uk/government/publications/20-years-of-the-national-minimum-wage">But you are wrong</a>. We can have a spirited discussion about whether the rate should increase, or whether it should be the same for younger workers, but the evidence shows the introduction of the minimum wage hasn't led to disaster.</p>
<p>Imagine that you fervently believe that the UK should "stop the boats". Have any of the current government policies worked to reduce those numbers? Is there any evidence that countries with "off-shore processing" receive fewer undesirables? Not that I've seen.  It looks like faster and fairer processing is both cheaper to the public purse and achieves policy goals better.</p>
<p>And so it goes.</p>
<p>I'm sure you can come up with a hundred more examples at various political extremities.  Some things work and some things don't. Getting bogged down in dogma doesn't help anyone.  Yes, I know that the founding document of your political philosophy says X - but at some point we have to put theory away and go with something practical.</p>
<p>This isn't me going all "<a href="https://www.theguardian.com/commentisfree/2017/oct/04/centrist-dad-labour-corbyn-neoliberal">Centrist Dad</a>". I'm not even sure I'd describe myself as a technocrat.</p>
<p>I just want evidence-based policy.</p>
]]></content>
		
					<link rel="replies" type="text/html" href="https://shkspr.mobi/blog/2024/02/forget-technocrats-lets-get-some-realitycrats/#comments" thr:count="5" />
			<link rel="replies" type="application/atom+xml" href="https://shkspr.mobi/blog/2024/02/forget-technocrats-lets-get-some-realitycrats/feed/atom/" thr:count="5" />
			<thr:total>5</thr:total>
			</entry>
		<entry>
		<author>
			<name>@edent</name>
					</author>

		<title type="html"><![CDATA[Actually, I *do* want IoT kitchen gadgets]]></title>
		<link rel="alternate" type="text/html" href="https://shkspr.mobi/blog/2024/02/actually-i-do-want-iot-kitchen-gadgets/" />

		<id>https://shkspr.mobi/blog/?p=49552</id>
		<updated>2024-02-04T12:11:20Z</updated>
		<published>2024-02-09T12:34:31Z</published>
		<category scheme="https://shkspr.mobi/blog" term="/etc/" /><category scheme="https://shkspr.mobi/blog" term="IoT" />
		<summary type="html"><![CDATA[There's a popular meme that Internet connected domestic appliances are a useless fad that no-one wants. I disagree. Obviously, a crappy oven with an app that upsells you cleaning products is a bit shit. As is a dishwasher that borks on firmware update and lets itself be hacked by the Eurasians. But those are just [&#8230;]]]></summary>

					<content type="html" xml:base="https://shkspr.mobi/blog/2024/02/actually-i-do-want-iot-kitchen-gadgets/"><![CDATA[<p>There's a popular meme that Internet connected domestic appliances are a useless fad that no-one wants.  I disagree.</p>
<p>Obviously, a crappy oven with an app that upsells you cleaning products is a bit shit. As is a dishwasher that borks on firmware update and lets itself be hacked by the Eurasians.  But those are just a symptom of profit-led development rather than placing a priority on user-needs.</p>
<p>Several years ago, <a href="https://shkspr.mobi/blog/2016/12/building-an-internet-connected-fridge/">I built an  Internet Connected Fridge</a>. This didn't have a touchscreen with a broken calendar integration, and it didn't use AI to scan my yoghurt for expiry dates.  All it did was send me an email when I'd left the door open too long.</p>
<p>You see I am old and forgetful. I can't always hear the machine's plaintive beep. My fridge doesn't let me adjust its tip, and I couldn't find a spring-mount which would fit. A simple push-notification is exactly the solution I wanted.</p>
<p>Similarly, my air-fryer, washing machine, dishwasher, bread-maker, and microwave are <em>quiet</em>.  I know I could start a timer on my phone every time I use them, but that's a bit of a faff. And doesn't work for non-timer devices like the rice-cooker and tumble-dryer.</p>
<p>IoT notifications are useful <em>to me</em>.</p>
<p>What about control?  That's a slightly more difficult subject.  Most of my gadgets already have a timer delay built in.  That allows me to set them to run <a href="https://shkspr.mobi/blog/2024/01/we-pay-12p-kwh-for-electricity-thanks-to-a-smart-tariff-and-battery/">when I know electricity is going to be cheaper</a>.  So I <em>could</em> set the air-fryer to have dinner ready for me at a specific time.  But if my journey home is delayed, I have no way to stop it.</p>
<p>To be honest, it is rare that I want to control something while I'm away from home.  I like that I can <a href="https://shkspr.mobi/blog/2020/01/boring-is-beautiful/">crank up my heating while on the bus home</a>, that's as far as it goes.</p>
<p>So I can see the utility for some people, but it isn't especially relevant to <em>my</em> needs.</p>
<h2 id='your-arguments-are-irrelevant'><a href='#your-arguments-are-irrelevant' class='heading-link'>Your arguments are irrelevant</a></h2>
<p>Whenever I talk about stuff like this, some weapons-grade bore feels compelled to pipe up and say "No! IoT is rubbish! The S stands for Security!! How did people cope before this??? Why not get off your arse and check manually???? ENSHITTIFICATION!!!!!!"</p>
<p>Let me be clear - I don't give a fuck about your opinion, random Internet man.  You don't get to police what desires people have. I am competent enough to set up my network securely. I am willing to spend a few hours fiddling with HomeAssistant to save me 3 minutes at a later date. Your opinion is unwanted and unnecessary.</p>
<p>I <em>like</em> technology. I <em>enjoy</em> playing and exploring. I <em>want</em> things to be slightly more convenient.</p>
<p>So, I've just bought some Matter smart plugs and am going to retrofit alerting to some of my legacy appliances. Fun times ahead!</p>
]]></content>
		
					<link rel="replies" type="text/html" href="https://shkspr.mobi/blog/2024/02/actually-i-do-want-iot-kitchen-gadgets/#comments" thr:count="9" />
			<link rel="replies" type="application/atom+xml" href="https://shkspr.mobi/blog/2024/02/actually-i-do-want-iot-kitchen-gadgets/feed/atom/" thr:count="9" />
			<thr:total>9</thr:total>
			</entry>
		<entry>
		<author>
			<name>@edent</name>
					</author>

		<title type="html"><![CDATA[The Seven Levels of Open Source]]></title>
		<link rel="alternate" type="text/html" href="https://shkspr.mobi/blog/2024/02/the-seven-levels-of-open-source/" />

		<id>https://shkspr.mobi/blog/?p=48953</id>
		<updated>2024-02-07T19:23:24Z</updated>
		<published>2024-02-08T12:34:22Z</published>
		<category scheme="https://shkspr.mobi/blog" term="/etc/" /><category scheme="https://shkspr.mobi/blog" term="Computer Science" /><category scheme="https://shkspr.mobi/blog" term="Open Source" />
		<summary type="html"><![CDATA[This isn't an original idea, but I needed to get it out of my brain. There are many different definitions of what "Open Source". We can have a lovely argument over a pint as to whether GPLv3 is too open or if a licence which hasn't been validated by the OSI counts. But, more fundamentally, [&#8230;]]]></summary>

					<content type="html" xml:base="https://shkspr.mobi/blog/2024/02/the-seven-levels-of-open-source/"><![CDATA[<p>This isn't an original idea, but I needed to get it out of my brain.</p>
<p>There are many different definitions of what "Open Source". We can have a lovely argument over a pint as to whether GPLv3 is <em>too</em> open or if a licence which hasn't been validated by the OSI counts. But, more fundamentally, I think Open Source roughly falls into <a href="https://www.nme.com/news/music/paul-mccartney-12-1188735">seven levels</a>.</p>
<p>These aren't in any particular order of importance. And feel free to argue in the comments if you think I've radically misunderstood something.</p>
<h2 id='1-look-but-dont-touch'><a href='#1-look-but-dont-touch' class='heading-link'>1. Look but don't touch</a></h2>
<p>This is the bare minimum. The source is "open" in that you can look at it, examine it, and possibly even learn from it. But that' is <em>it</em>.</p>
<p>You can't redistribute it. You can't edit it. You can't build on it.  But you can see it.</p>
<h2 id='2-do-what-thou-wilt'><a href='#2-do-what-thou-wilt' class='heading-link'>2. Do What Thou Wilt</a></h2>
<p>The source is yours to do with as you please.  You can distribute it, build on it, print it out, eat it, use it in a weapons system.  There are no restriction.</p>
<p>Have fun!</p>
<h2 id='3-do-as-you-would-be-done-by'><a href='#3-do-as-you-would-be-done-by' class='heading-link'>3. Do As You Would Be Done By</a></h2>
<p>There is ponderous legal language, but it all adds up to one thing - you have to comply with our requirements.</p>
<p>Perhaps they say "only redistribute with this licence" or maybe "you must make everything this touches open".  Either way, you aren't quite as free to do what you want.</p>
<p>Have fun - but don't piss off anyone.</p>
<h2 id='4-id-rather-you-didnt'><a href='#4-id-rather-you-didnt' class='heading-link'>4. I'd rather you didn't</a></h2>
<p>These are less often seen, but becoming more common.  You are free to do anything you want with this code... unless you're someone we don't like.</p>
<p>Some code says you can't use it for military purposes, others restrict its usage if you're going to be racist with it, and some say it can only be used by a particular class of people.</p>
<p>These licences are controversial. Openness means this is for everybody.  Sure, no one likes the thought of their code being in a bomb. But your agents of imperial oppression are my freedom fighters.</p>
<h2 id='5-contributors-welcome'><a href='#5-contributors-welcome' class='heading-link'>5. Contributors Welcome</a></h2>
<p>We're on GitHub! We actively want you to participate!  Not only is the code open - but so is the community!  Anyone with an IDE and an idea is welcome to pitch in!</p>
<p>Come play!</p>
<h2 id='6-blessed-contributors'><a href='#6-blessed-contributors' class='heading-link'>6. Blessed Contributors</a></h2>
<p>We're open! But only certain people are allowed to contribute.  All others will be shunned.</p>
<p>This is the model Google takes with Android - fully open, but good luck getting even a comma changed.  There's also a popular open source project which requires its contributors to be religious!</p>
<p>This is open; but only for the chosen few.</p>
<h2 id='7-the-future'><a href='#7-the-future' class='heading-link'>7. The Future</a></h2>
<p>There is something coming that you and I cannot understand. Deep in the darkest trenches of the Internet comes a new breed of hacker. Their social norms diverge from ours. They aren't beholden to the old ways and care not for our pettifogging traditions.</p>
<p>The are building a new form of Open Source. Something that reflects the needs and concerns of their generation, rather than the tired problems of ours. Old farts will harrumph and grumble about how it isn't <em>proper</em> Open Source - and moan that the youngling don't fear their elders any more.</p>
<p>But, make no mistake, the future is coming and it doesn't need your old-fashioned opinions.</p>
]]></content>
		
					<link rel="replies" type="text/html" href="https://shkspr.mobi/blog/2024/02/the-seven-levels-of-open-source/#comments" thr:count="2" />
			<link rel="replies" type="application/atom+xml" href="https://shkspr.mobi/blog/2024/02/the-seven-levels-of-open-source/feed/atom/" thr:count="2" />
			<thr:total>2</thr:total>
			</entry>
		<entry>
		<author>
			<name>@edent</name>
					</author>

		<title type="html"><![CDATA[VR Game Review: Labyrinth deLux – A Crusoe Quest]]></title>
		<link rel="alternate" type="text/html" href="https://shkspr.mobi/blog/2024/02/vr-game-review-labyrinth-delux-a-crusoe-quest/" />

		<id>https://shkspr.mobi/blog/?p=49538</id>
		<updated>2024-02-04T16:35:39Z</updated>
		<published>2024-02-07T12:34:39Z</published>
		<category scheme="https://shkspr.mobi/blog" term="/etc/" /><category scheme="https://shkspr.mobi/blog" term="Game Review" /><category scheme="https://shkspr.mobi/blog" term="metaverse" /><category scheme="https://shkspr.mobi/blog" term="oculus" /><category scheme="https://shkspr.mobi/blog" term="vr" />
		<summary type="html"><![CDATA[I love single player VR puzzle games. Especially ones with no timers, baddies, or jump-scares. I just want to play against myself. Labyrinth deLux is brilliant. The puzzle is simple enough - point lasers at mirrors, then align mirrors until they point at the target. You've almost certainly played a 2D version of this. But [&#8230;]]]></summary>

					<content type="html" xml:base="https://shkspr.mobi/blog/2024/02/vr-game-review-labyrinth-delux-a-crusoe-quest/"><![CDATA[<p>I love single player VR puzzle games. Especially ones with no timers, baddies, or jump-scares.  I just want to play against myself.</p>
<p>Labyrinth deLux is brilliant.  The puzzle is simple enough - point lasers at mirrors, then align mirrors until they point at the target. You've almost certainly played a 2D version of this.</p>
<p>But it has a mind-bending 3D layout which requires you to continually walk on the ceiling to adjust your perspective. The UI is, thankfully, not chunder-inducing. You point and click at a surface and then smoothly float and reorient to it.</p>
<p>The plot is a bit silly - your spaceship crashes and you have a mysterious destiny or some nonsense. But the voice acting is good and you can ignore the story without consequence.</p>
<p>Graphics are lo-fi charming, which helps prevent the usual VR weirdness.  Excessive graphics quality just shines a light on how bad the Meta Quest 2 is. But the Minecraft style helps keep the FPS up.</p>
<p>There are 16 levels of increasing difficulty - which took me about 3½ hours to complete:</p>
<p><img loading="lazy" decoding="async" src="https://shkspr.mobi/blog/wp-content/uploads/2024/02/deluxe.jpg" alt="VR screenshot showing how long each level took." width="1024" height="576" class="aligncenter size-full wp-image-49558" /></p>
<p>At £6, it's probably one of the cheapest games on Oculus - and excellent value for money.</p>
<div class="embed-vimeo" style="text-align: center;"><iframe loading="lazy" src="https://player.vimeo.com/video/706544311" width="620" height="349" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe></div>
]]></content>
		
					<link rel="replies" type="text/html" href="https://shkspr.mobi/blog/2024/02/vr-game-review-labyrinth-delux-a-crusoe-quest/#comments" thr:count="1" />
			<link rel="replies" type="application/atom+xml" href="https://shkspr.mobi/blog/2024/02/vr-game-review-labyrinth-delux-a-crusoe-quest/feed/atom/" thr:count="1" />
			<thr:total>1</thr:total>
			</entry>
		<entry>
		<author>
			<name>@edent</name>
					</author>

		<title type="html"><![CDATA[Movie Review: Oppenheimer ★★⯪☆☆]]></title>
		<link rel="alternate" type="text/html" href="https://shkspr.mobi/blog/2024/02/movie-review-oppenheimer/" />

		<id>https://shkspr.mobi/blog/?p=49532</id>
		<updated>2024-02-02T09:56:12Z</updated>
		<published>2024-02-06T12:34:00Z</published>
		<category scheme="https://shkspr.mobi/blog" term="/etc/" /><category scheme="https://shkspr.mobi/blog" term="Movie Review" />
		<summary type="html"><![CDATA[Oppenheimer is... fine? I guess? For ever gorgeously composed shot, there's a minute of plodding exposition. For every heart-breaking moment of self-doubt, there's a minute of plodding exposition. For every celebrity cameo, there's a minute of plodding exposition. That's why this is a decidedly average film. Every single actor is incredible - stuffed as it [&#8230;]]]></summary>

					<content type="html" xml:base="https://shkspr.mobi/blog/2024/02/movie-review-oppenheimer/"><![CDATA[<p><img decoding="async" src="https://shkspr.mobi/blog/wp-content/uploads/2024/02/Oppenheimer-poster.jpg" alt="Movie poster." width="200" class="alignleft size-full wp-image-49533" />Oppenheimer is... fine? I guess?  For ever gorgeously composed shot, there's a minute of plodding exposition. For every heart-breaking moment of self-doubt, there's a minute of plodding exposition. For every celebrity cameo, there's a minute of plodding exposition.</p>
<p>That's why this is a decidedly average film. Every single actor is incredible - stuffed as it is with Oscar<sup>©®™</sup> winners and nominees.  But some of the dialogue is just <em>risible</em> - for example, the revelation that JFK had a bit-part in the proceedings goes like this:</p>
<blockquote><p>
  Aide: A young guy trying to make a name for himself, didn't like what you did to Oppenheimer.<br />
  Strauss: What's his name?<br />
  Aide: Kennedy. John F. Kennedy!
</p></blockquote>
<p>I mean, the actor delivers it well, but it isn't exactly a masterful plot construct.</p>
<p>Robert Downey JR - freed from the franchise shackles - gets to act once again. He is outstanding - which is incredible seeing as the only dialogue he has is narrating what's happening in flashbacks.</p>
<p>Similarly, when it comes to the naming of the "Trinity" site, Oppenheimer just recites a random bit of poetry. There's no rhyme or reason to it (and it seems none was given in reality) so it sticks out compared to the (endless) exposition in other scenes.</p>
<p>Every single frame is a masterpiece. Nolan and his team know exactly how to get the most out of a film shoot. But the rapid changes to aspect ratio are a constant distraction.</p>
<p>The mini-physics lectures contain less detail than a do-your-own-research YouTube video.  Which is fine; this is a piece of entertainment, not a physics lesson. The constitutional implications of Senate confirmation are less educational than a West Wing episode. Which is fine; this is a piece of entertainment, not a civics lesson. The exploration of workers rights and the rise of intellectual communism is dismissed without thought. Which is fine; this is a piece of entertainment, not a political philosophy lecture.</p>
<p>But because the film tries to cram in <em>so</em> much, it ends up not really exploring anything.</p>
<p>And on it goes. For every moment of beauty and delight, there's a moment of clock-watching and eye-rolling.</p>
<p>I preferred Barbie.</p>
]]></content>
		
					<link rel="replies" type="text/html" href="https://shkspr.mobi/blog/2024/02/movie-review-oppenheimer/#comments" thr:count="3" />
			<link rel="replies" type="application/atom+xml" href="https://shkspr.mobi/blog/2024/02/movie-review-oppenheimer/feed/atom/" thr:count="3" />
			<thr:total>3</thr:total>
			</entry>
		<entry>
		<author>
			<name>@edent</name>
					</author>

		<title type="html"><![CDATA[Safelinks are a fragile foundation for publishing]]></title>
		<link rel="alternate" type="text/html" href="https://shkspr.mobi/blog/2024/02/safelinks-are-a-fragile-foundation-for-publishing/" />

		<id>https://shkspr.mobi/blog/?p=49515</id>
		<updated>2024-02-04T10:00:57Z</updated>
		<published>2024-02-05T12:34:10Z</published>
		<category scheme="https://shkspr.mobi/blog" term="/etc/" /><category scheme="https://shkspr.mobi/blog" term="microsoft" /><category scheme="https://shkspr.mobi/blog" term="privacy" /><category scheme="https://shkspr.mobi/blog" term="web" />
		<summary type="html"><![CDATA[Microsoft loves you and wants to protect you. So every time you receive an email with a link in it, Microsoft Outlook helpfully rewrites it so that it goes through their "safelinks" system. Safelinks allow your administrator, or someone at Microsoft, to stop you visiting a link which is malicious or suspicious. Rather than going [&#8230;]]]></summary>

					<content type="html" xml:base="https://shkspr.mobi/blog/2024/02/safelinks-are-a-fragile-foundation-for-publishing/"><![CDATA[<p>Microsoft loves you and wants to protect you. So every time you receive an email with a link in it, Microsoft Outlook helpfully rewrites it so that it goes through their "<a href="https://learn.microsoft.com/en-us/microsoft-365/security/office-365-security/safe-links-about?view=o365-worldwide#safe-links-settings-for-email-messages">safelinks</a>" system.</p>
<p>Safelinks allow your administrator, or someone at Microsoft, to stop you visiting a link which is malicious or suspicious.  Rather than going to <code>example.com</code>, your link now goes to <code>safelinks.protection.outlook.com/?url=example.com</code>.</p>
<p>Hurrah! If you accidentally click on a naughty link you won't cause chaos and ructions.</p>
<p>Except, there's a tiny problem.  People like to copy and paste links that they receive. Someone sends an email which says "here's the link to that report you asked for" which then gets copied into a document or a web page.</p>
<p>For example, I was reading this <a href="https://assets.publishing.service.gov.uk/media/65b236c81702b10013cb1289/DHSC-Annual-report-and-accounts-2022-2023-web-accessible.pdf">official document from the UK's Department of Health and Social Care</a>. Slap bang in the middle is a link to another report:</p>
<p><img loading="lazy" decoding="async" src="https://shkspr.mobi/blog/wp-content/uploads/2024/01/safelinks-fs8.png" alt="Screenshot showing a document. The cursor hovers over a link. The pop up shows a safelinks URl." width="903" height="275" class="aligncenter size-full wp-image-49516" /></p>
<p>That forces <em>everyone</em> who visits that link to go through Microsoft's proxy. That <em>might</em> protect users if a link later becomes suspicious. But, more likely, it will be used in analytics to further profile users who click on links. It also undermines a user's ability to see the final destination of a link unless they can manually URl-decode content in their head.</p>
<p>It appears that every large organisation which uses Microsoft is prone to this failure.  Lots of UK Government departments publish content with safelinks:<br />
<img loading="lazy" decoding="async" src="https://shkspr.mobi/blog/wp-content/uploads/2024/01/safelinks-govuk-fs8.png" alt="Screenshot of Google search results for GOV.UK sites." width="1050" height="825" class="aligncenter size-full wp-image-49519" /></p>
<p>The US Military too:<br />
<img loading="lazy" decoding="async" src="https://shkspr.mobi/blog/wp-content/uploads/2024/01/safelinks-mil-fs8.png" alt="Screenshot of Google search results for US Military sites." width="1050" height="750" class="aligncenter size-full wp-image-49521" /></p>
<p>It's all over Twitter:<br />
<img loading="lazy" decoding="async" src="https://shkspr.mobi/blog/wp-content/uploads/2024/01/safelink-twitter-fs8.png" alt="Screenshot of Twitter search results." width="748" height="561" class="aligncenter size-full wp-image-49518" /></p>
<p>And there are hundreds of academic works infested:<br />
<img loading="lazy" decoding="async" src="https://shkspr.mobi/blog/wp-content/uploads/2024/01/safelinks-scholar-fs8.png" alt="Screenshot of Google Scholar results." width="1025" height="785" class="aligncenter size-full wp-image-49517" /></p>
<p>Look, I <em>get</em> why people do this. They copy a link from an email, paste it in, click it, and it works. No one writes raw HTML by hand, nor should they have to. Our WYSIWYG tools work really well and hide all the mumbo-jumbo. Copy editors look at text; not hypertext.  It's only nerds like me who hover over a link before clicking on it.</p>
<p>Perhaps I should stop worrying? Perhaps it is OK that Microsoft intercepts the clicks from people all around the world? Perhaps they can competently run a proxy which detects and blocks inappropriate content? Perhaps they won't ever abuse that facility?</p>
<p>Here's my prediction. In the next five or so years, Microsoft is going to accidentally shut off <code>*.safelinks.protection.outlook.com</code> and a million copy-and-pasted links across the web are going to break.</p>
<p>Think I'm over-reacting?  A decade ago, <a href="https://shkspr.mobi/blog/2013/08/the-end-of-ms-tag/">Microsoft got rid of their MS Tag product</a> and, shortly after, <a href="https://scanbuy.com/microsoft-tag-transition-to-scanlife">all their proxy links were shut off</a>.  Similarly, other proxies like <a href="https://shkspr.mobi/blog/2014/08/mcafees-failure-of-trust/">McAfee have shut down with little warning</a>.</p>
<p>Or maybe <a href="https://www.zdnet.com/article/microsoft-has-a-subdomain-hijacking-problem/">Microsoft's sub-domains will be hijacked</a>?</p>
<p>Either way, if you work in digital publishing, please make sure that your links point directly to the content that you want; not to Microsoft's safelinks service.</p>
]]></content>
		
					<link rel="replies" type="text/html" href="https://shkspr.mobi/blog/2024/02/safelinks-are-a-fragile-foundation-for-publishing/#comments" thr:count="13" />
			<link rel="replies" type="application/atom+xml" href="https://shkspr.mobi/blog/2024/02/safelinks-are-a-fragile-foundation-for-publishing/feed/atom/" thr:count="13" />
			<thr:total>13</thr:total>
			</entry>
		<entry>
		<author>
			<name>@edent</name>
					</author>

		<title type="html"><![CDATA[Book Review - How Sex Changed the Internet and the Internet Changed Sex: An Unexpected History by Samantha Cole ★★★⯪☆]]></title>
		<link rel="alternate" type="text/html" href="https://shkspr.mobi/blog/2024/02/book-review-how-sex-changed-the-internet-and-the-internet-changed-sex-an-unexpected-history-by-samantha-cole/" />

		<id>https://shkspr.mobi/blog/?p=49505</id>
		<updated>2024-01-29T17:04:38Z</updated>
		<published>2024-02-04T12:34:27Z</published>
		<category scheme="https://shkspr.mobi/blog" term="/etc/" /><category scheme="https://shkspr.mobi/blog" term="Book Review" />
		<summary type="html"><![CDATA[This book is a rather pleasing wander through two interconnected topics. From the earliest chat rooms (A/S/L?) all the way to haptic-feedback in the Metaverse, this breezes through the way sex has advanced the technology and the resulting impact technology has had on sex. The book is well illustrated - skirting a fine line between [&#8230;]]]></summary>

					<content type="html" xml:base="https://shkspr.mobi/blog/2024/02/book-review-how-sex-changed-the-internet-and-the-internet-changed-sex-an-unexpected-history-by-samantha-cole/"><![CDATA[<p><img decoding="async" src="https://shkspr.mobi/blog/wp-content/uploads/2024/01/61039m4y2AL._SL500_.jpg" alt="Book cover featuring a peach emoji." width="200" class="alignleft size-full wp-image-49507" />This book is a rather pleasing wander through two interconnected topics. From the earliest chat rooms (A/S/L?) all the way to haptic-feedback in the Metaverse, this breezes through the way sex has advanced the technology and the resulting impact technology has had on sex.</p>
<p>The book is well illustrated - skirting a fine line between being overly prudish and unnecessarily graphic.</p>
<p>There are diversions into the religious weirdos behind some online dating sites, the original cam-girls, and BBS culture. The scatter-shot approach gives a wide but sometimes shallow look at all the ways the two topics intermingle. There aren't many interviews with the people involved - and it does rather rely on second-hand reporting.</p>
<p>As with a lot of books, only America exists. There are a few scattered mentions of Europe.  But, apparently, the Internet didn't change sex for the majority of the world.  All the censorship discussion goes via the cultural hegemony.</p>
<p>Weirdly, there's nothing about sexting or about how the Internet has liberated (some) disabled people.</p>
<p>It does point to some other interesting resources, such as the documentary "<a href="https://www.facebook.com/codelikeagirlau/videos/985614161797010/">Losing Lena</a>".</p>
<p>On a separate note - this is a <em>beautifully</em> formatted ebook. I find lots of publishers don't pay much attention to the ways the specific medium works. So it is nice to see some care an attention to images and captions. But, weirdly, the references aren't inline. The reference section links out to <a href="https://workman.com/HowSexChangedtheInternet">https://workman.com/HowSexChangedtheInternet</a> - but that doesn't contain any actual links.</p>
<p>It is a good look through some of the steps (and missteps) which has led us to the Internet we have now.</p>
]]></content>
		
					<link rel="replies" type="text/html" href="https://shkspr.mobi/blog/2024/02/book-review-how-sex-changed-the-internet-and-the-internet-changed-sex-an-unexpected-history-by-samantha-cole/#comments" thr:count="0" />
			<link rel="replies" type="application/atom+xml" href="https://shkspr.mobi/blog/2024/02/book-review-how-sex-changed-the-internet-and-the-internet-changed-sex-an-unexpected-history-by-samantha-cole/feed/atom/" thr:count="0" />
			<thr:total>0</thr:total>
			</entry>
		<entry>
		<author>
			<name>@edent</name>
					</author>

		<title type="html"><![CDATA[A (tiny, incomplete, single user, write-only) ActivityPub server in PHP]]></title>
		<link rel="alternate" type="text/html" href="https://shkspr.mobi/blog/2024/02/a-tiny-incomplete-single-user-write-only-activitypub-server-in-php/" />

		<id>https://shkspr.mobi/blog/?p=49490</id>
		<updated>2024-02-04T10:48:49Z</updated>
		<published>2024-02-03T12:34:51Z</published>
		<category scheme="https://shkspr.mobi/blog" term="/etc/" /><category scheme="https://shkspr.mobi/blog" term="ActivityPub" /><category scheme="https://shkspr.mobi/blog" term="fediverse" /><category scheme="https://shkspr.mobi/blog" term="mastodon" /><category scheme="https://shkspr.mobi/blog" term="php" /><category scheme="https://shkspr.mobi/blog" term="Symfony" />
		<summary type="html"><![CDATA[I've written an ActivityPub server which only allows you to post messages to your followers. That's all it does. It won't record favourites or reposts. There's no support for following other accounts or receiving replies. It cannot delete or update posts nor can it verify signatures. It doesn't have a database or any storage beyond [&#8230;]]]></summary>

					<content type="html" xml:base="https://shkspr.mobi/blog/2024/02/a-tiny-incomplete-single-user-write-only-activitypub-server-in-php/"><![CDATA[<p>I've written <a href="https://github.com/edent/location-activitypub-symfony/">an ActivityPub server which <em>only</em> allows you to post messages to your followers</a>.  That's all it does.  It won't record favourites or reposts. There's no support for following other accounts or receiving replies.  It cannot delete or update posts nor can it verify signatures. It doesn't have a database or any storage beyond flat files.</p>
<p>But it will happily send messages and allow itself to be followed.</p>
<p>This shows that it is <em>totally</em> possible to broadcast fully-featured ActivityPub messages to the Fediverse with minimal coding skills and modest resources.</p>
<h2 id='why'><a href='#why' class='heading-link'>Why</a></h2>
<p>I wanted to create <a href="https://shkspr.mobi/blog/2024/01/rebuilding-foursquare-for-activitypub-using-openstreetmap/">a service a bit like FourSquare</a>.  For this, I needed an ActivityPub server which allows posting geotagged locations to the Fediverse.</p>
<p>I didn't want to install a fully-featured server with lots of complex parts. So I (foolishly) decided to write my own. I had a lot of trouble with HTTP Signatures.  Because they are cursed and I cannot read documentation. But mostly the cursed thing.</p>
<h2 id='how'><a href='#how' class='heading-link'>How</a></h2>
<p>Creating a minimum viable Mastodon instance can be done with <a href="https://justingarrison.com/blog/2022-12-06-mastodon-files-instance/">half a dozen static files</a>. That gets you an account that people can see. They can't follow it or receive any posts though.</p>
<p>I wanted to use PHP to build an interactive server. PHP is supported everywhere and is simple to deploy.  Luckily, <a href="https://rknight.me/blog/building-an-activitypub-server/">Robb Knight has written an excellent tutorial</a>, so I ripped off his code and rewrote it for Symfony.</p>
<p>The structure is relatively straightforward.</p>
<ul>
<li><code>/.well-known/webfinger</code> is a static file which gives information about where to find details of the account.</li>
<li><code>/[username]</code> is a static file which has the user's metadata, public key, and links to avatar images.</li>
<li><code>/following</code> and <code>/followers</code> are also static files which say how many users are being followed / are following.</li>
<li><code>/posts/[GUID]</code> a directory with JSON files saved to disk - each ones contains the published ActivityPub note.</li>
<li><code>/photos/</code> is a directory with any uploaded media in it.</li>
<li><code>/outbox</code> is a list of all the posts which have been published.</li>
<li><code>/inbox</code> is an <em>external</em> API endpoint. An ActivityPub server sends it a follow request, the endpoint then POSTs a cryptographically signed Accept message to the follower's inbox. The follower's inbox address is saved to disk.</li>
<li><code>/logs</code> is a listing of all the messages received by the inbox.</li>
<li><code>/new</code> is a password protected page which lets you write a message. This is then sent to...</li>
<li><code>/send</code> is an <em>internal</em> API endpoint. It constructs an ActivityPub note, with attached location metadata, and POSTs it to each follower's inbox with a cryptographic signature.</li>
</ul>
<p>That's it.</p>
<p>The front-end grabs my phone's geolocation and shows the 25 nearest places within 100 metres. One click and the page posts to the <code>/send</code> endpoint which then publishes a message saying I'm checked in.  It is also possible to attach to the post a short message and a single photo with alt text.</p>
<p>There's no database. Posts are saved as JSON documents. Images are uploaded to a directory. It is single-user, so there is no account management.</p>
<h2 id='what-works'><a href='#what-works' class='heading-link'>What Works</a></h2>
<ul>
<li style="list-style-type: '✅ ';">Users can find the account.</li>
<li style="list-style-type: '✅ ';">Users can follow the account and receive updates.</li>
<li style="list-style-type: '✅ ';">Posts contain geotag metadata.</li>
<li style="list-style-type: '✅ ';">Posts contain a description of the place.</li>
<li style="list-style-type: '✅ ';">Posts contain an OSM link to the place.</li>
<li style="list-style-type: '✅ ';">Posts contain a custom message.</li>
<li style="list-style-type: '✅ ';">Posts autolink #Hashtags (sort of).</li>
<li style="list-style-type: '✅ ';">Posts can have an image attached to them.</li>
<li style="list-style-type: '✅ ';">Messages to the inbox are recorded (but not yet integrated).</li>
</ul>
<h2 id='todo'><a href='#todo' class='heading-link'>ToDo</a></h2>
<ul>
<li>My account only has a few dozen followers, some of whom share the same sever. Even with cURL multi handle, it takes time to post to several servers.</li>
<li>It posts plain text. It doesn't autolink websites</li>
<li>Hashtags are linked when viewed remotely, but they don't go anywhere locally.</li>
<li>There's no language selection - it is hard-coded to English.</li>
<li>The outbox isn't paginated.</li>
<li>The UI looks crap - but it is only me using it.</li>
<li>There's only a basic front-page showing a map of all my check-ins.</li>
<li>Replies are logged, but there's no easy way to see them.</li>
<li>Doesn't show any metadata about the place being checked-in to. It could use the item's website (if any) or hashtags for the type of amenity it is.</li>
<li>No way to handle being unfollowed.</li>
<li>No way to remove servers which have died.</li>
<li>Probably lots more.</li>
</ul>
<h2 id='other-resources'><a href='#other-resources' class='heading-link'>Other Resources</a></h2>
<p>I found these resources helpful while creating this project:</p>
<ul>
<li><a href="https://tinysubversions.com/notes/activitypub-tool/">MVP ActivityPub in Python on Glitch</a></li>
<li><a href="https://seb.jambor.dev/posts/understanding-activitypub/">Understanding ActivityPub Part 1: Protocol Fundamentals</a></li>
<li><a href="https://flak.tedunangst.com/post/activity-notes">Activity Notes</a></li>
<li><a href="https://docs.gotosocial.org/en/latest/federation/federating_with_gotosocial/">Federating with GoToSocial</a></li>
</ul>
<h2 id='whats-next'><a href='#whats-next' class='heading-link'>What's Next?</a></h2>
<p>I've <a href="https://github.com/mastodon/mastodon/issues/29002">raised an issue on Mastodon</a> to see if they can support showing locations in posts. Hopefully, one day, they'll allow adding locations and then I can shut this down.</p>
<p>The code needs tidying up - it is very much a scratch-my-own-itch development. Probably riddled with bugs and security holes.</p>
<p>World domination?</p>
<h2 id='where'><a href='#where' class='heading-link'>Where</a></h2>
<p>You can <a href="https://github.com/edent/location-activitypub-symfony/">laugh at my code on GitHub</a>.</p>
<p>You can <a href="https://location.edent.tel/">look at my check-ins on a map</a>.</p>
<p>You can follow my location on the Fediverse at <code>@edent_location@location.edent.tel</code></p>
]]></content>
		
					<link rel="replies" type="text/html" href="https://shkspr.mobi/blog/2024/02/a-tiny-incomplete-single-user-write-only-activitypub-server-in-php/#comments" thr:count="11" />
			<link rel="replies" type="application/atom+xml" href="https://shkspr.mobi/blog/2024/02/a-tiny-incomplete-single-user-write-only-activitypub-server-in-php/feed/atom/" thr:count="11" />
			<thr:total>11</thr:total>
			</entry>
	</feed>
