diff --git a/.CopyFromServer.sh b/.CopyFromServer.sh
new file mode 100755
index 0000000..aef4685
--- /dev/null
+++ b/.CopyFromServer.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+cp /Main/Server/www/Drive/Misc/Scripts/MemosViewer.php ./
diff --git a/MemosViewer.php b/MemosViewer.php
new file mode 100644
index 0000000..7c18353
--- /dev/null
+++ b/MemosViewer.php
@@ -0,0 +1,171 @@
+
+require 'Res/FastVoltMarkdown.php';
+use FastVolt\Helper\Markdown;
+$markdown = Markdown::new();
+
+$instance = 'https://memos.octt.eu.org';
+$id = $_GET['id'];
+$uid = $_GET['uid'];
+
+$footer = "";
+
+if ( !$id && !$uid ) {
+ $memos = array_slice( explode( "\n\tmemos/", file_get_contents( "{$instance}/memos.api.v1.MemoService/ListMemos", false, stream_context_create([ 'http' => [
+ 'method' => 'POST',
+ 'header' => 'Content-Type: application/grpc-web+proto',
+ 'content' => urldecode('%00%00%00%008%08%10%1A4row_status%20%3D%3D%20%22NORMAL%22%20%26%26%20visibilities%20%3D%3D%20%5B\'PUBLIC\'%5D'),
+ ]]) ) ), 1 );
+ echo "
Latest public memos from {$instance}:
";
+ foreach ( $memos as $memo ) {
+ $uid = explode( '%', explode( '%12%16', urlencode($memo) )[1] )[0];
+ if ( $uid ) {
+ //$user = explode( '*', explode( 'users/', $memo )[1] )[0];
+ echo "- ${uid}
";
+ }
+ }
+ echo "
{$footer}";
+ return;
+}
+
+if ( !$id ) {
+ // as of writing this, there is no JSON API to get a memo by its uid
+ // so, we first get the numeric id by sloppily parsing the GRPC API
+ $id = explode( '%', urlencode(explode( 'memos/', file_get_contents( "{$instance}/memos.api.v1.MemoService/SearchMemos", false, stream_context_create([ 'http' => [
+ 'method' => 'POST',
+ 'header' => 'Content-Type: application/grpc-web+proto',
+ 'content' => urldecode('%00%00%00%00!%0A%1Fuid%20%3D%3D%20%22' . $uid . '%22'),
+ ]]) ) )[1]))[0];
+}
+
+// we always use the numeric id to get memo data via the JSON API
+$memo = json_decode(file_get_contents("{$instance}/api/v1/memos/{$id}"));
+$user = json_decode(file_get_contents("{$instance}/api/v1/{$memo->creator}"));
+
+// patch the Markdown before parsing it, so that output is quasi-consistent with Memos
+$content = '';
+$inblock = false;
+$lines = explode( "\n", $memo->content );
+foreach ( $lines as $line ) {
+ if ( str_starts_with( $line, '```' ) ) {
+ $inblock = !$inblock;
+ } else if ( str_starts_with( $line, '#' ) ) {
+ // prevent hashtags from being interpreted as headings
+ $firstword = explode( ' ', str_replace( "\t", ' ', $line ) )[0];
+ if ( $firstword !== '#' ) {
+ $content .= " {$firstword}";
+ $line = substr( $line, strlen($firstword) );
+ }
+ }
+ $content .= $line . "\n";
+ if ( !$inblock ) {
+ $content .= "\n";
+ }
+}
+
+$markdown->setContent($content);
+$content = $markdown->toHtml();
+
+$htmlparts = explode( '', $content );
+$content = array_shift($htmlparts);
+foreach ( $htmlparts as $part ) {
+ [$inside, $after] = explode( '
', $part );
+ //$content .= '' . $inside . '
' . $after;
+ $content .= '' . html_entity_decode($inside)) . '">' . $after;
+}
+
+$warning = '';
+$base = file_get_contents($instance /* . '/m/' . $uid */);
+if ( $_GET['structure'] === 'original' ) {
+ $warning = '';
+} /* else if ( $_GET['structure'] === 'standalone' ) {
+ $base = "
+
+
+
+
+
+
+";
+} */
+
+$nickname = htmlspecialchars($user->nickname);
+$pagetitle = "Memo by {$nickname}";
+$pagedescription = htmlspecialchars($memo->content);
+$htmlimage = implode( '', array_slice( explode( '"', implode( '', array_slice( explode( '
";
+}
+
+$meta = "
+{$pagetitle}
+
+
+
+
+{$htmlimage}
+
+";
+
+$body = "
+
+
+{$content}
+
+{$footer}
+
";
+
+$base = str_replace( 'Memos', '', $base );
+$base = str_replace( "", "{$meta}", $base );
+$base = str_replace( "", "{$body}
", $base );
+echo $base;