This page permanently redirects to gemini://gemini.techrights.org/git/tr-git/Gemini/.

Git browser: Gemini/

This page presents code associated with the module/unit named above.

=> Summary of changes
=> Back to Git index
=> Licence (AGPLv3)

Gemini/gemini-planet-irc.sh

#!/bin/bash

yesterday=$(date --date yesterday +"%Y-%m-%d")
today=$(date --date today +"%Y-%m-%d")

echo "(ℹ) Planet Gemini updated. Latest complete date at gemini://gemini.techrights.org/planet/othercapsules/${yesterday}.gmi and so far today at gemini://gemini.techrights.org/planet/othercapsules/${today}.gmi with 3-day aggregate at gemini://gemini.techrights.org/planet/othercapsules/allinone.gmi"  > /home/glr/ii-1.8/techrightslocal/irc.techrights.org/#techrights/in

Gemini/gemini-fetch-urls-from-rss.pl

#!/usr/bin/perl -T

# 2021-01-04
# updated 2021-02-12
# XML RSS and Atom feed web scraper,
# feed it URLs for feeds plus a date-time stamp
# entries will be parsed and can saved in a file
# local times will be converted to UTC

use utf8;
use Getopt::Std;
use Time::ParseDate;
use Time::Piece;
use XML::Feed;
use URI;
use LWP::UserAgent;
use HTTP::Response::Encoding;
use HTML::TreeBuilder::XPath;
binmode *STDOUT, ':utf8';
use English;

use strict;
use warnings;

our $VERBOSE = 0;
$OUTPUT_AUTOFLUSH=1;

# work-arounds for 'wide character' error from wrong UTF8
binmode(STDIN,  ":encoding(utf8)");
binmode(STDOUT, ":encoding(utf8)");

our %opt;
getopts('ad:ho:uv', \%opt);

my $script = $0;

if (defined($opt{'h'})) {
    &usage($script);
}

if (defined($opt{'v'})) {
    $VERBOSE++;
}

my $output = '';

if (defined($opt{'o'})) {
    # XXX needs proper sanity checking for path and filename at least
    $output = $opt{'o'};
    $output =~ s/[\0-\x1f]//g;
    if ($output =~ /^([-\/\w\.]+)$/) {
        $output = $1;
    } else {
        die("Bad path or file name: '$output'\n");
    }
}

my $utc = 0;	# treat input as a local time and convert to UTC
if (defined($opt{'u'})) {
    $utc = 1;	# treat input as UTC without conversion
}

# accept feed entries only after this date, default is yesterday
my $sdts;
if (defined($opt{'d'})) {
    $sdts = parsedate($opt{'d'}, GMT=>$utc);	# interpret input
} else {
    $sdts = parsedate('yesterday');		# default to yesteday
}
my $t = Time::Piece->strptime($sdts, '%s');	# time in epoch seconds

print STDERR qq(S=$sdts\n)
 if ($VERBOSE);

print STDERR qq(D=),$t->strftime("%a, %d %b %Y %H:%M:%S %Z"),qq(\n)
    if ($VERBOSE);

# process feed URLs one at a time, keep track of how many there were
my $count = 0;
while (my $url = shift) {
    next if ($url =~ /^\s*#/);	# skip comments

    print STDERR qq(\nU=$url\n)
	if ($VERBOSE);

    # get the feed and work on it
    my $r = &get_feed($t,$url,$output);

    if ($r) {
	$count++;
    } else {
	print STDERR qq(Could not find feed at URL: "$url"\n);
    }
}

# print qq(\n
\n\n) if ($count); exit(1) unless ($count); exit(0); sub usage { my ($script) = (@_); $script =~ s/^.*\///; print <parse(URI->new($uri)); }; if ($@) { print STDERR $@,qq(\n); print STDERR qq( Failed feed for '$uri'\n); return(0); } # move past otherwise fatal failures in parsing titles, jump to next my $feed_title; eval { $feed_title = $feed->title; }; if ($@) { print STDERR qq( Failed title for '$uri'\n); return(0); } my $feed_modified = $feed->modified; # unsupported my $feed_format = $feed->format; print STDERR qq(\tT=$feed_title\n) if ($VERBOSE); print STDERR qq(\tF=$feed_format\n) if ($VERBOSE); # feed was acquired now go through and find the URLs it contains &read_entries($t,$feed,$output); return(1); } # find URLs within a feed sub read_entries { my ($t,$feed,$output) = (@_); $t = parsedate($t); # if there is an output file set it up for writing my $out; if($output) { # choose append or overwrite mode for file my $mode = ''; if (defined($opt{'a'})) { $mode = '>>'; } else { $mode = '>'; } # open file for output in chosen mode and handling UTF-8 open($out, $mode, $output) or die("Could not open '$output' for appending: $!\n"); binmode($out, ":encoding(utf8)"); } my $count = 0; foreach my $entry ($feed->entries) { # entry time my $ft = $entry->{entry}{pubDate} || $entry->issued || $entry->modified; # entry time in seconds my $et = parsedate($ft) || 0; next unless($et =~ /^\d+$/ && $et >= $t ); # these links are sometimes redirections from proxies my ($base, $content) = &fetch_page($entry->link) or die("Missing content from '",$entry->link,"'\n"); next if ($base eq -1 || $content eq -1); next if ($base =~ /^\d+/ && $base<0); print STDERR qq(Fetched:),substr($base,0,30),qq(\n) if ($VERBOSE); my $uri = URI->new($base) or warn("Bad address, '$base', could not form URI\n"); $uri->query(undef); $uri->fragment(undef); my $site = $uri->authority; print STDERR qq(A=$site\n) if ($VERBOSE); my $o = $uri->canonical; if ($o) { $count++; if($output) { print $out $o,qq(\n); } else { print $o,qq(\n); } } print STDERR qq(\t\t),$base,qq(\n) if ($VERBOSE); } if($output) { close $out; } return(1); } sub fetch_page { my ($uri) = (@_); # this part could just as well be replaced by wget or curl via system() my $ua = LWP::UserAgent->new; $ua->agent("TechRights-Gemini-Bot/0.1"); my $request = HTTP::Request->new(GET => $uri); my $result = $ua->request($request); if ($result->is_success) { return($result->base, $result->decoded_content); } else { warn("Could not open '$uri' : ", $result->status_line, "\n"); return(-1,-1); } return(-1,-1); }

Gemini/agate-tcpdump-logger.service

[Unit]
Description=Logging for the Agate Gemini Server
After=network.target
After=agate.target

[Service]
User=root
Type=simple
Environment=STARTED_BY_SYSTEMD=true
ExecStart=/usr/local/sbin/agate-tcpdump-logger.sh

ExecReload=/bin/kill -HUP $MAINPID
ExecStop=/bin/kill $MAINPID
KillMode=control-group
PrivateTmp=true
Restart=on-failure
Restart=5s

[Install]
WantedBy=multi-user.target
Alias=gemini-logger.service

Gemini/gemini-get-and-convert-wiki-from-html.pl

#!/usr/bin/perl

# 2021-06-30
# scrape a designated wiki page and convert to gemtext

use utf8;
use Getopt::Std;
use File::Glob ':bsd_glob';
use HTML::TreeBuilder::XPath;
use HTML::Entities;
use URI;

binmode *STDIN,  ':utf8';
# binmode *STDOUT, ':utf8';

use English;

use warnings;
use strict;

$OUTPUT_AUTOFLUSH = 1;

our %opt;
my $script = $0;

getopts('Chvw:', \%opt);

&usage($script) if ($opt{'h'});

# iterate through any parmeters passed on the command line,
# treat as file names and allow globbing
my @filenames;
while (my $file = shift) {
    if ($file eq '-') {
	# allow processing of STDIN
	push(@filenames, '-');
	if($opt{'v'}) { print qq(F= *STDIN\n); }

    } else {
	my @files = bsd_glob($file);
	foreach my $f (@files) {
	    push(@filenames, $f);
	    if($opt{'v'}) { print qq(F=$f\n); }
	}
    }
}

my $outfile = $opt{'w'} || '';
chomp($outfile);
print "1: ", $outfile,qq(\n) if($opt{'v'});
# lll
( $outfile ) = ($outfile =~ m/^([\:\"\'\/\-\_\.\(\)\p{Word}\x7f-\xff]+)$/ );
print "2: ", $outfile,qq(\n) if($opt{'v'});

# quit if no files were listed
&usage($script) if($#filenames < 0);

# process each file, skipping turd files, hidden files, and temp files
while (my $infile = shift(@filenames)) {
    next if ($infile =~ m/~$/);
    next if ($infile =~ m/^\.\.?(?!\/)/);
    next if ($infile =~ m/^#/);

    my $result = &wiki_to_gemini($infile) || exit(1);

    if ($outfile) {
        my ($dir) = ($outfile =~ m{^(.*/)[^/]+$});
        if (! -d $dir) {
            die("Directory '$dir' does not exist.\n");
        }
        if (! -w $dir) {
            die("Directory '$dir' is not writable.\n");
        }
        open(my $o, '>:utf8', $outfile)
            or die("Could not open '$outfile' for writing: $!\n");

        if ($opt{'v'}) { print qq(Writing to "$outfile"\n); }
        print $o $result;
        print $o qq(\n),qq(-)x10,qq(\n);
        print $o qq(=>\t/\tTechrights\n);
        print $o qq(➮ Sharing is caring.  );
        print $o qq(Content is available under CC-BY-SA.);

        close($o);
    } else {
        print $result;
        print qq(\n),qq(-)x10,qq(\n);
        print qq(=>\t/\tTechrights\n);
        print qq(➮ Sharing is caring.  );
        print qq(Content is available under CC-BY-SA.\n);
    }
}

exit(0);

sub usage {
    my ($script) = (@_);
    $script =~ s/^.*\///g;
    print qq(Usage: $script [-Chv] [-w file] file\n);
    print <new;
    $xhtml->implicit_tags(1);
    $xhtml->no_space_compacting(0);

    $xhtml->parse_file($input)
        or die("Could not parse '$file' : $!\n");

    my $result = '';

    my %prefix = (
        'h1' => "#\t● ",
        'h2' => "##\t●● ",
        'h3' => "###\t●●● ",
        'h4' => "###\t●●●● ",
        'h5' => "###\t●●●●● ",
        'h6' => "###\t●●●●●● "
        );

    my @content = ();
    for my $wiki ($xhtml->findnodes('//div[@id="bodyContent"]')) {
	$wiki->detach();
	push(@content, $wiki);
    }

    my $post = HTML::Element->new('div');
    $post->push_content(@content);

    if ($opt{'C'}) {
	print qq(Deleting TOC\n) if($opt{'v'});
	for my $toc ($post->findnodes('//table[@id="toc"]')) {
	    $toc->delete;
	}
    }

    for my $script ($post->findnodes('//script')) {
	$script->delete;
    }

    for my $div ($post->findnodes('//div/div/div')) {
	$div->delete;
    }

    for my $table ($post->findnodes('//table[@id="toc"]')) {
	$table->delete;
    }

    for my $table ($post->findnodes('//table[@class="wikitable"]')) {
	$table->delete;
    }

    for my $a ($post->findnodes('//a[@name and not(@href)]')) {
	$a->replace_with_content($a->as_text());
    }

    foreach my $hn (1 .. 5) {
	# format headings
	$hn = qq(h$hn);
	for my $heading ($post->findnodes("//$hn")) {
	    my $h ="\n";
	    $h .= $prefix{$hn} if (defined($prefix{$hn}));
	    $h .= $heading->as_text."\n";
	    my $tmp = HTML::Element->new('~literal', 'text'=>$h);
	    $heading->replace_with($tmp);
	}
    }

    for my $ul ($post->findnodes('.//ul')) {
	for my $li ($ul->findnodes('.//li')) {
	    my $listitem = '';
	    if ($li->findnodes('text()')) {
		$listitem = $li->as_text;
		chomp($listitem);
	    }

	    my @anchors = ();
	    for my $a ($li->findnodes('.//a[@href and not(ancestor::pre)]')) {
		my $href = $a->attr('href') || next;

		my $uri = URI->new($href)->canonical || next;
		my $text = $a->as_trimmed_text();

		# normalize TR addresses
 		if(!$uri->scheme) {
		    $uri->scheme('http');
		    $uri->host('techrights.org');
		} elsif($uri->scheme eq 'mailto') {
		    next;
		} elsif($uri->host eq 'boycottnovell.com') {
		    $uri->host('techrights.org');
		}

		# mark external link or else convert TR links to Gemini
		if ($uri->host ne 'techrights.org') {
		    $text = '↺ '.$text;
		} elsif ($uri->host eq 'techrights.org' && ! $opt{'C'}) {
		    if($uri->path =~ m{\.pdf$}i) {
			$text = '↺ '.$text;
		    } else {
			$uri->host('gemini.techrights.org');
			$uri->scheme('gemini');
			unless ($uri->path =~ m/\/$/) {
			    my $p = $uri->path;
			    $uri->path($p.'/');
			}
		    }
		}

		my $link = "\n=>\t".$uri->canonical."\t".$text;
		push(@anchors, $link);

	    }
	    $listitem = $listitem.join("\n", @anchors).qq(\n);
	    my $tmp =  HTML::Element->new('~literal', 'text'=>$listitem);
	    $li->replace_with($tmp);
	}
    }

    for my $pp ($post->findnodes(".//p")) {
	my @anchors=();
	for my $a ($pp->findnodes('.//a[@href and not(ancestor::pre)]')) {
	    my $href = $a->attr('href');
	    print "	H=$href\n" if ($opt{'v'});
	    next if ($href =~ m/^#/);
	    $href =~ s/\s/%20/g;

	    my $uri = URI->new($href)->canonical || next;
	    my $text = $a->as_trimmed_text();

	    # normalize TR addresses
	    if(!$uri->scheme) {
		$uri->scheme('http');
		$uri->host('techrights.org');
	    } elsif($uri->scheme eq 'mailto') {
		next;
	    } elsif($uri->host eq 'boycottnovell.com') {
		$uri->host('techrights.org');
	    }

	    # mark external link or else convert TR links to Gemini
	    if ($uri->host ne 'techrights.org') {
		$text = '↺ '.$text;
	    } elsif ($uri->host eq 'techrights.org' && ! $opt{'C'}) {
		if($uri->path =~ m{\.pdf$}) {
		    $text = '↺ '.$text;
		} else {
		    $uri->host('gemini.techrights.org');
		    $uri->scheme('gemini');
		    unless ($uri->path =~ m/\/$/) {
			my $p = $uri->path;
			$uri->path($p.'/');
		    }
		}
	    }

	    my $link = "\n=>\t".$uri->canonical."\t".$text;

	    push (@anchors, $link);
	}
	my $d;
	if($#anchors >= 0) {
	    $d = $pp->as_text.qq(\n)
		.join("\n", @anchors ).qq(\n);
	} else {
	    $d = $pp->as_text.qq(\n);
	}

	my $tmp =  HTML::Element->new('~literal', 'text' => $d);
	$pp->replace_with($tmp);
    }

    for my $hr ($post->findnodes("//hr")) {
	my $tmp =  HTML::Element->new('~literal', 'text'=>"\n");
	$hr->replace_with($tmp);
    }

    $result = $post->as_XML_indented;

    $post->destroy;
    $xhtml->destroy;

    unless($is_stdin) {
	close($input);
    }

    while ($result =~ s/<[^>]+>/ /g) { 1 };	# dead tags
    while ($result =~ s/^\s+#/#/g) { 1 };	# fix headings
    while ($result =~ s/^\s+$//) { 1 };		# trailing white space
    while ($result =~ s/^\n\n\n/\n\n/gm) { 1 };	# vertical white space

    $result = decode_entities($result);
    return($result);
}

Gemini/gemini-log-journalctl.sh

#!/bin/sh

# 2022-01-18

# try to extract gemini log info from systemd's journalctl
# and convert it to gemtext

since=$(ps -p $(pgrep -o agate) --no-headers -o start)

d=$(date +"%F")
    #--until "$(date -d "$d -1 day" +'%F 23:59')" \

# set -xv
journalctl -q -u agate \
    --since "$(date -d "$d -1 day" +'%F 00:00')" \
| awk '
    $7~/INFO/ && $11 ~/"gemini/ {
        sub(/^"/, "", $11)
        sub(/"$/, "", $11)
        sub(/\/index.gmi$/, "/", $11)
        sub(/\/feed$/, "/feed/", $11)
        sub(/gemini:\/\/gemini.techrights.org:1965\//,
            "gemini://gemini.techrights.org/", $11)
        url[$11]++
        c++
    }
    END {
        print(c " Unique Requests\n");
        for (u in url) {
            printf("%s\t%4d\t%s\n", u,url[u],u)
        }
    }' \
| sort -k2,2nr -k3,3 \
| head -n 31 \
| awk -v s="$since" 'FNR==1 {
            print("# Techrights Gemini Capsule Usage\n")
            print("Running since " s "\n")
            print("## Top accessed pages yesterday:\n")
        }
        $2 {
            printf("=> %s\t%4d\t%s\n",$3,$2,$3)
        }
        END {
            print("\n=> / Techrights")
            printf("➮ Sharing is caring.  ")
            printf("Content is available under CC-BY-SA.\n")
        }' \
> /home/gemini/gemini/stats/top-usage-yesterday.gmi

Gemini/gemini-main-index-template.sh

#!/bin/sh

# 2021-02-15
# a template for generating the main index.gmi file

PATH=/home/gemini/bin:/usr/local/bin:/usr/bin:/bin

# set -xv
d=$(date +'%F')

# get formatted dates
mm=$(date +"%Y/%m/index.gmi\t%B" -d "$d");		# this month
# my=$(date +"%Y/%m/index.gmi\t%B" -d "$d -1 month");	# last month
my=$(date --date="$(date -d $d +%Y-%m-15) -1 month" +"%Y/%m/index.gmi\t%B");
dy=$(date +"%Y/%m/%d" -d "$d -1 day");		# yesterday
dd=$(date +"%Y/%m/%d" -d "$d");			# today
mbn=$(date +"bulletins/%Y/%m/index.gmi\t%Y %B" -d "$d");	  # this month
mb1=$(date +"bulletins/%Y/%m/index.gmi\t%Y %B" -d "$(date -d $d +%Y-%m-15) -1 month");
mb2=$(date +"bulletins/%Y/%m/index.gmi\t%Y %B" -d "$d -2 month");
mb3=$(date +"bulletins/%Y/%m/index.gmi\t%Y %B" -d "$d -3 month");
mb4=$(date +"bulletins/%Y/%m/index.gmi\t%Y %B" -d "$d -4 month");
mb5=$(date +"bulletins/%Y/%m/index.gmi\t%Y %B" -d "$d -5 month");
mb6=$(date +"bulletins/%Y/%m/index.gmi\t%Y %B" -d "$d -6 month");
mb7=$(date +"bulletins/%Y/%m/index.gmi\t%Y %B" -d "$d -7 month");
mb8=$(date +"bulletins/%Y/%m/index.gmi\t%Y %B" -d "$d -8 month");
mb9=$(date +"bulletins/%Y/%m/index.gmi\t%Y %B" -d "$d -9 month");
mb10=$(date +"bulletins/%Y/%m/index.gmi\t%Y %B" -d "$d -10 month");
mb11=$(date +"bulletins/%Y/%m/index.gmi\t%Y %B" -d "$d -11 month");
mb12=$(date +"bulletins/%Y/%m/index.gmi\t%Y %B" -d "$d -12 month");
mb13=$(date +"bulletins/%Y/%m/index.gmi\t%Y %B" -d "$d -13 month");
mb14=$(date +"bulletins/%Y/%m/index.gmi\t%Y %B" -d "$d -14 month");
mb15=$(date +"bulletins/%Y/%m/index.gmi\t%Y %B" -d "$d -15 month");
mb16=$(date +"bulletins/%Y/%m/index.gmi\t%Y %B" -d "$d -16 month");
mb17=$(date +"bulletins/%Y/%m/index.gmi\t%Y %B" -d "$d -17 month");
mb18=$(date +"bulletins/%Y/%m/index.gmi\t%Y %B" -d "$d -18 month");
mb19=$(date +"bulletins/%Y/%m/index.gmi\t%Y %B" -d "$d -19 month");
mb20=$(date +"bulletins/%Y/%m/index.gmi\t%Y %B" -d "$d -20 month");
mb21=$(date +"bulletins/%Y/%m/index.gmi\t%Y %B" -d "$d -21 month");
mb22=$(date +"bulletins/%Y/%m/index.gmi\t%Y %B" -d "$d -22 month");
mb23=$(date +"bulletins/%Y/%m/index.gmi\t%Y %B" -d "$d -23 month");
mb24=$(date +"bulletins/%Y/%m/index.gmi\t%Y %B" -d "$d -24 month");
mb25=$(date +"bulletins/%Y/%m/index.gmi\t%Y %B" -d "$d -25 month");
mb26=$(date +"bulletins/%Y/%m/index.gmi\t%Y %B" -d "$d -26 month");
mb27=$(date +"bulletins/%Y/%m/index.gmi\t%Y %B" -d "$d -27 month");
mb28=$(date +"bulletins/%Y/%m/index.gmi\t%Y %B" -d "$d -28 month");
mb29=$(date +"bulletins/%Y/%m/index.gmi\t%Y %B" -d "$d -29 month");
mb30=$(date +"bulletins/%Y/%m/index.gmi\t%Y %B" -d "$d -30 month");
mb31=$(date +"bulletins/%Y/%m/index.gmi\t%Y %B" -d "$d -31 month");
mb32=$(date +"bulletins/%Y/%m/index.gmi\t%Y %B" -d "$d -32 month");

tmplinks=$(mktemp --suffix "-index-links-gemini") || exit 1
tmpindex=$(mktemp --suffix "-index-idx-gemini") || exit 1
trap "rm -f -- $tmplinks; rm -f -- $tmpindex;" EXIT

set -e

# find the index files for those dates and extract the links
# find /home/gemini/gemini \
# 	-type f \
# 	\( -path "*/$dy/index.gmi" -o -path "*/$dd/index.gmi" \) \
# 	-exec sed -nre '/Whole Month/,${/Whole Month/d;p}' {} \; \
# 	| sort -r \
# 	> $tmplinks
#

# find /home/gemini/gemini/202? \
# 	-mindepth 3 -maxdepth 3 -type f -name index.gmi -print \
#     | sort -r \
#     | head -n 3 \
#     | xargs awk '$1=="=>"\
# 	&&$2~/^.[0-9]{4}.[0-9]{2}.[0-9]{2}.[[:alnum:]]{2}/' \

find /home/gemini/gemini/202? \
	-mindepth 3 -maxdepth 3 -type f -name index.gmi -print \
    | sort -r \
    | head -n 3 \
    | xargs perl -a -n -e \
        '$F[1]=~m{/[0-9]{4}/[0-9]{2}/[0-9]{2}/\w+} && print' \
    > $tmplinks

# cat -n $tmp

cat << EOF > $tmpindex
Welcome to Techrights, the Gemini Capsule!  ⛬

We also have a Web proxy at http://gemini.techrights.org/

# Overview

=> /intro/ Introduction (ℹ)
=> /about/ About this capsule ⌂
=> /archives.gmi Capsule archives §
=> /irc.gmi Contact us (IRC) 📧

=> /logo/logo.png Site logo (if your Gemini client supports that)

# Articles from Techrights (GemText)

## Latest Articles in Techrights

$(cat $tmplinks)

 • Please note the above might be slightly out of date, they are synched from HTTP every 3 hours ⟳  Next update at approximately $(TZ=UTC date -d "+3 hours" +"%H:%M UTC").

## Monthly Archives

=> $(echo $mm) 🕮
=> $(echo $my) 🕮
=> /archive/ Older (2006 to present) 📚

# Additional Sections

##  Organised Information

=> /wiki/ Techrights wiki ▤

## Code

=> /git/ Techrights git G

## Videos

=> /latest-videos/ Latest Techrights videos ◉
=> /videos/ All Techrights videos ◉

## Capsule Stats

=> stats/index.gmi Statistics for this month 🗠

## Syndication

=> /feed.xml RSS/XML ♲
=> /planet/ Planet Gemini ♲

### Gemini Feeds (GemText)

=> /feed/ Feed as GemText ♲
=> /daily-feed/ Daily Feeds ♲

## IPFS (Decentralised)

=> /ipfs/ IPFS Archive 🗜

## Bulletins from Techrights (Plain Text)

=> $(echo -n $mbn), $(echo $mbn | cut -c1-18 \
        | sed 's|bulletins|/home/gemini/gemini|' \
        | xargs du -sh | cut -f1) 🖃
=> $(echo -n $mb1), $(echo $mb1 | cut -c1-18 \
        | sed 's|bulletins|/home/gemini/gemini|' \
        | xargs du -sh | cut -f1) 🖃
=> $(echo -n $mb2), $(echo $mb2 | cut -c1-18 \
        | sed 's|bulletins|/home/gemini/gemini|' \
        | xargs du -sh | cut -f1) 🖃
=> $(echo -n $mb3), $(echo $mb3 | cut -c1-18 \
        | sed 's|bulletins|/home/gemini/gemini|' \
        | xargs du -sh | cut -f1) 🖃
=> $(echo -n $mb4), $(echo $mb4 | cut -c1-18 \
        | sed 's|bulletins|/home/gemini/gemini|' \
        | xargs du -sh | cut -f1) 🖃
=> $(echo -n $mb5), $(echo $mb5 | cut -c1-18 \
        | sed 's|bulletins|/home/gemini/gemini|' \
        | xargs du -sh | cut -f1) 🖃
=> $(echo -n $mb6), $(echo $mb6 | cut -c1-18 \
        | sed 's|bulletins|/home/gemini/gemini|' \
        | xargs du -sh | cut -f1) 🖃
=> $(echo -n $mb7), $(echo $mb7 | cut -c1-18 \
        | sed 's|bulletins|/home/gemini/gemini|' \
        | xargs du -sh | cut -f1) 🖃
=> $(echo -n $mb8), $(echo $mb8 | cut -c1-18 \
        | sed 's|bulletins|/home/gemini/gemini|' \
        | xargs du -sh | cut -f1) 🖃
=> $(echo -n $mb9), $(echo $mb9 | cut -c1-18 \
        | sed 's|bulletins|/home/gemini/gemini|' \
        | xargs du -sh | cut -f1) 🖃
=> $(echo -n $mb10), $(echo $mb10 | cut -c1-18 \
        | sed 's|bulletins|/home/gemini/gemini|' \
        | xargs du -sh | cut -f1) 🖃
=> $(echo -n $mb11), $(echo $mb11 | cut -c1-18 \
        | sed 's|bulletins|/home/gemini/gemini|' \
        | xargs du -sh | cut -f1) 🖃
=> $(echo -n $mb12), $(echo $mb12 | cut -c1-18 \
        | sed 's|bulletins|/home/gemini/gemini|' \
        | xargs du -sh | cut -f1) 🖃
=> $(echo -n $mb13), $(echo $mb13 | cut -c1-18 \
        | sed 's|bulletins|/home/gemini/gemini|' \
        | xargs du -sh | cut -f1) 🖃
=> $(echo -n $mb14), $(echo $mb14 | cut -c1-18 \
        | sed 's|bulletins|/home/gemini/gemini|' \
        | xargs du -sh | cut -f1) 🖃
=> $(echo -n $mb15), $(echo $mb15 | cut -c1-18 \
        | sed 's|bulletins|/home/gemini/gemini|' \
        | xargs du -sh | cut -f1) 🖃
=> $(echo -n $mb16), $(echo $mb16 | cut -c1-18 \
        | sed 's|bulletins|/home/gemini/gemini|' \
        | xargs du -sh | cut -f1) 🖃
=> $(echo -n $mb17), $(echo $mb17 | cut -c1-18 \
        | sed 's|bulletins|/home/gemini/gemini|' \
        | xargs du -sh | cut -f1) 🖃
=> $(echo -n $mb18), $(echo $mb18 | cut -c1-18 \
        | sed 's|bulletins|/home/gemini/gemini|' \
        | xargs du -sh | cut -f1) 🖃
=> $(echo -n $mb19), $(echo $mb19 | cut -c1-18 \
        | sed 's|bulletins|/home/gemini/gemini|' \
        | xargs du -sh | cut -f1) 🖃
=> $(echo -n $mb20), $(echo $mb20 | cut -c1-18 \
        | sed 's|bulletins|/home/gemini/gemini|' \
        | xargs du -sh | cut -f1) 🖃
=> $(echo -n $mb21), $(echo $mb21 | cut -c1-18 \
        | sed 's|bulletins|/home/gemini/gemini|' \
        | xargs du -sh | cut -f1) 🖃
=> $(echo -n $mb22), $(echo $mb22 | cut -c1-18 \
        | sed 's|bulletins|/home/gemini/gemini|' \
        | xargs du -sh | cut -f1) 🖃
=> $(echo -n $mb23), $(echo $mb23 | cut -c1-18 \
        | sed 's|bulletins|/home/gemini/gemini|' \
        | xargs du -sh | cut -f1) 🖃
=> $(echo -n $mb24), $(echo $mb24 | cut -c1-18 \
        | sed 's|bulletins|/home/gemini/gemini|' \
        | xargs du -sh | cut -f1) 🖃
=> $(echo -n $mb25), $(echo $mb25 | cut -c1-18 \
        | sed 's|bulletins|/home/gemini/gemini|' \
        | xargs du -sh | cut -f1) 🖃
=> $(echo -n $mb26), $(echo $mb26 | cut -c1-18 \
        | sed 's|bulletins|/home/gemini/gemini|' \
        | xargs du -sh | cut -f1) 🖃
=> $(echo -n $mb27), $(echo $mb27 | cut -c1-18 \
        | sed 's|bulletins|/home/gemini/gemini|' \
        | xargs du -sh | cut -f1) 🖃
=> $(echo -n $mb28), $(echo $mb28 | cut -c1-18 \
        | sed 's|bulletins|/home/gemini/gemini|' \
        | xargs du -sh | cut -f1) 🖃
=> $(echo -n $mb29), $(echo $mb29 | cut -c1-18 \
        | sed 's|bulletins|/home/gemini/gemini|' \
        | xargs du -sh | cut -f1) 🖃
=> $(echo -n $mb30), $(echo $mb30 | cut -c1-18 \
        | sed 's|bulletins|/home/gemini/gemini|' \
        | xargs du -sh | cut -f1) 🖃
=> $(echo -n $mb31), $(echo $mb31 | cut -c1-18 \
        | sed 's|bulletins|/home/gemini/gemini|' \
        | xargs du -sh | cut -f1) 🖃
=> $(echo -n $mb32), $(echo $mb32 | cut -c1-18 \
        | sed 's|bulletins|/home/gemini/gemini|' \
        | xargs du -sh | cut -f1) 🖃

 • The World Wide Web 🗺  always contains the latest bulletins
 • Sizes at the top are for all bulletins (for a given month) combined

### ➮ Sharing is caring. Content is available under CC-BY-SA. ⟲

EOF

cat $tmpindex
rm -f -- $tmplinks
rm -f -- $tmpindex
trap - exit
exit

Gemini/gemini-inventory.pl

#!/usr/bin/perl

# 2021-02-22
# surveys the designated directory for articles
# groups them by day in daily summaries for each day, then
# creates a feed of recent articles
# creates another feed of recent daily summaries

use utf8;
use Getopt::Std;
use File::Glob ':bsd_glob';
use Path::Iterator::Rule;	# libpath-iterator-rule-perl
use POSIX qw(strftime);
use Date::Calc qw(Add_Delta_Days Month_to_Text);

use strict;
use warnings;

our %opt;
getopts('A:v', \%opt);

print qq(Verbose mode\n) if ($opt{'v'});

# iterate through any parmeters passed on the command line,
# treat as file names and allow globbing
my @filenames;
while (my $file = shift) {
    my @files = bsd_glob($file);
    foreach my $f (@files) {
	if (-d $f && -w $f) {
	    push(@filenames, $f);
	} else {
	    warn("'$f' is not a directory and/or not writable.\n");
	}
    }
}

if ($#filenames < 0) {
    print qq(Example:\n);
    my ($script) = ($0 =~ m{([^/]+)$});
    print qq($script /home/foobar/\n);
    exit(1);
}

print qq(Paths:\n\t),join("\n\t",@filenames),qq(\n) if ($opt{'v'});

# prepare a search through the file system
my $rule = Path::Iterator::Rule->new;
$rule->name("*.gmi");
$rule->min_depth(4);
$rule->max_depth(6);
# $rule->contents_match(qr/^#+\s+/);	# this rule does not match correctly

# find the sought after file names
my @files = $rule->all( @filenames );

print qq(Files:\n\t),join("\n\t",@files),qq(\n) if ($opt{'v'});

# collect titles and file names from the found issues/numbers and volumes
our %issue = ();
for my $f ( @files ) {
    $f =~ s{//} {/}g;
    print qq(Found: $f\n) if ($opt{'v'});
    if (my ( $base, $year, $month, $day, $issue )
	= ($f =~ m{^(.*)/(\d{4})/(\d{2})/(\d{2})/(.*)/(\w+\.gmi)$})) {

	my $title = &process_file($f);
	my $gindex = "/$year/$month/$day/index.gmi";
	my $gfile  = "/$year/$month/$day/$issue/index.gmi";
	my $link = "=>\t$gfile\t$month/$day $title";

	print " /$year/$month/$day/$issue/\n" if ($opt{'v'});

	push (@{$issue{$gindex}{'links'}}, $link);
	$issue{$gindex}{'base'} = $base;
    }
}

&daily_summaries();
&monthly_summaries();
if($opt{'A'}) {
    my ($archive) = ($opt{'A'} =~ /^([\w\/\_\-]+)$/);
    if($archive) {
	print qq(Archive = $archive\n) if ($opt{'v'});
	&grand_archive($archive);
    }
}

exit(0);

sub process_file {
    my ($file) = (@_);
    my $title = '';

    open(my $article, '<:encoding(UTF-8)', $file)
	or die("Could not open file '$file' for reading: $!\n");

    while (my $line = <$article>) {
	if ($line =~ m/^#+\s+(.*)$/) {
	    # skip plain dates
	    next if($line =~ m/^\#+\s+\S+\s+\d{2}\.\d{2}/);

	    chomp($line);
	    ($title) = ($line=~m/^\#+\s+\S+\s+(.*)$/);
	    last;
	}
    }
    close($article);

    return($title||'');
}

sub daily_summaries {
    # daily summaries
    my @key = sort keys %issue;
    for my $k (@key) {

	my $base = $issue{$k}{'base'};

	my $out;
	open ($out, ">:utf8", "$base$k")
	    or warn("Could not open file '$base$k' for output: error: $!\n");

	my ($title) = ($k =~ m/^(.*)index.gmi$/);
	my ($month) = ($title =~ m/^\/(.*)\/\d+\/$/);
	print $out qq(#\tTechRights\t$title\n\n);
	print $out qq(=>\t/index.gmi\tTechrights\n);
	print $out qq(=>\t../index.gmi\tThe Whole Month $month\n\n);
	for my $link(@{$issue{$k}{'links'}}) {
	    print $out qq($link\n);
	}
	print $out qq(\n);
	close($out);
    }

    # save the latest data as a sort of feed
    my $k = $key[$#key];
    my $base = $issue{$k}{'base'};
    $base =~ s|\/$||;
    my $feed = "$base/feed/";
    unless (-e $feed) {
	mkdir($feed,0755)
	    or die("Could not create directory '$feed': $!\n");
    } elsif (! -d $feed) {
	die("Not a directory: '$feed' \n");
    } elsif (! -w $feed) {
	die("Cannot write to directory '$feed'\n");
    }

    my $out;
    open ($out, ">:utf8", $feed.'index.gmi')
	or die("Could not open file '".$base.$feed.
		"index.gmi' for output: error: $!\n");

    #
    my ($title) = ($k =~ m/^(.*)index.gmi$/);
    my ($month) = ($title =~ m/^\/(.*)\/\d+\/$/);

    # iso-8601 format
    $title =~ s|^/||;
    $title =~ s|/$||;
    $title =~ s|/|-|g;
    my $latest = $title;

    print $out qq(#\tTechrights\n\n);
    print $out qq(#\tDaily Feed of Recent Articles as of $title\n\n);
    print $out qq(=>\t/index.gmi\tTechrights\n\n);
    # add the current day's links
    for my $link(@{$issue{$k}{'links'}}) {
	$link =~ s|\b\d{2}/\d{2}\s+||;
	$link =~ s|^(=>\s*\S+)(\s+)|$1$2$title |;
	print $out qq($link\n);
    }
    print $out qq(\n);

    # add the previous day's links too
    $k = $key[$#key-1];
    ($title) = ($k =~ m/^(.*)index.gmi$/);

    # iso-8601 format
    $title =~ s|^/||;
    $title =~ s|/$||;
    $title =~ s|/|-|g;

    for my $link(@{$issue{$k}{'links'}}) {
	$link =~ s|\b\d{2}/\d{2}\s+||;
	$link =~ s|^(=>\s*\S+)(\s+)|$1$2$title |;
	print $out qq($link\n);
    }

    print $out qq(\n=>\t/$month/index.gmi\tThe Whole Month $month\n\n);

    # or for GMT formatted appropriately for your locale:
    my $updatednow = strftime "Last updated %F %H:%M UTC\n", gmtime;
    print $out $updatednow;

    close($out);

    # feed of daily summaries
    $feed = $base.'/daily-feed/index.gmi';
    open ($out, ">:utf8", $feed)
	or die("Daily-feed: Could not open file '".$feed
	       ."' for output: error: $!\n");

    print $out qq(#\tTechrights\n\n);
    print $out qq(#\tFeed of Recent Daily Summaries\n\n);
    print $out qq(=>\t/index.gmi\tTechrights\n\n);

    my ($y, $m, $d) = split(/-/, $latest);
    for my $i (0 .. 10) {
	my ($dy, $dm, $dd) = Add_Delta_Days($y, $m, $d, -$i);
	my $index = sprintf("/%04d/%02d/%02d/index.gmi", $dy,$dm,$dd);
	if (-f $base.$index) {
	    printf $out ("=> %s\t%04d-%02d-%02d\n", $index,$dy,$dm,$dd);
	}
    }
    print $out qq(\n).$updatednow;
    close($out);

    return(1);
}

sub monthly_summaries {
    # monthly summaries
    my %volume = ();
    for my $k (sort keys %issue) {
	my $base = $issue{$k}{'base'};

	# K=/2021/01/01/index.gmi
	my ($title) = ($k =~ m/^(\/\d{4}\/\d{2}\/)/);
	my ($month) = ($title);
	my ($year)  = ($month =~ m/^(\d{4})\//);

	next unless ($month);

	$volume{$month}{'title'} = $title;
	$volume{$month}{'base'}  = $base;

	for my $link(@{$issue{$k}{'links'}}) {
	    push(@{$volume{$month}{'links'}}, $link);
	}
    }

    for my $m (sort keys %volume) {
	my $title = $volume{$m}{'title'};
	my $base  = $volume{$m}{'base'};
	my ($year,$month) = ( $title =~ m/^\/(\d{4})\/(\d{2})/);

	my $out;

	open ($out, ">:utf8", "$base$m"."index.gmi")
	    or warn("Could not open file '$base$m' for output: error: $!\n");

	print $out qq(#\tTechrights\t$title\n\n);
	print $out qq(=>\t/index.gmi\tTechrights\n\n\n);
	# print $out qq(=>\t../index.gmi\tThe Whole Year $year\n\n);

	my $count = 0;
	for my $link (@{$volume{$m}{'links'}}) {
	    print $out qq($link\n);
	    $count++;
	    print $out qq(\n) unless ($count % 5);
	    print $out qq(\n) unless ($count % 10);
	}

	# append navigation for next and previous months, if they exits
	my $next_month = $month+1>12 ? 1 : $month+1;
	my $next_year = $next_month < $month ? $year+1 : $year;

	my $prev_month = $month-1<1 ? 12 : $month-1;
	my $prev_year = $prev_month > $month ? $year-1 : $year;

	# next and previous keys to look for
	my $next_key = sprintf("/%04d/%02d/", $next_year, $next_month);
	my $prev_key = sprintf("/%04d/%02d/", $prev_year, $prev_month);

	if(defined($volume{$next_key}) or defined($volume{$prev_key})){
	    print $out qq(\n),qq(-)x10,qq(\n);

	    if(defined($volume{$next_key})){
		print $out qq(=>\t),$next_key,qq(index.gmi\tNext Month\n);
	    }
	    if(defined($volume{$prev_key})){
		print $out qq(=>\t),$prev_key,qq(index.gmi\tPrevious Month\n);
	    }
	}

	close($out);
    }
}

sub grand_archive {
    my ($archive) = (@_);

    my $latest_year=0;
    my %volume = ();
    for my $k (sort keys %issue) {
	my ($year,$month) = ($k =~ m/^\/(\d{4})\/(\d{2})\//);
	next unless ($month);

	$volume{$year}{$month}++;
	$latest_year=$year if ($year > $latest_year);
    }

    my $out;

    my $f = "$archive/index.gmi";
    unless(-e $archive) {
	# ought to be made recursive
        mkdir($archive,0755)
	    or die("Could not create directory '$archive': $!\n");
    }
    open ($out, ">:utf8", "$f")
	or die("Could not open file '$f' for output: error: $!\n");

    print $out qq(#\tArchive of All Articles as of $latest_year\n\n);
    print $out qq(=>\t/\t ↩ back to Techrights (Main Index)\n);

    for my $y (reverse sort keys %volume) {
	print $out qq(\n# $y\n\n);
	for my $m (reverse sort keys %{$volume{$y}}) {
	    my $month = Month_to_Text($m,1);
	    print $out qq(=> /$y/$m/\t$month $y\n);
	}
    }
    close($out);
}

Gemini/gemini-git-update.sh.orig

#!/bin/sh

# 2021-09-13
# update read-only git archive

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

# set -x
set -e

test -d /home/gemini/gemini/git
test -w /home/gemini/gemini/git

if test -d /home/gemini/gemini/git/tr-git; then
	cd /home/gemini/gemini/git/tr-git
	git fetch --all
	git reset --hard
	git pull
else
	cd /home/gemini/gemini/git
	git clone ssh://tr-git-read-only/home/git/tr-git/
	git pull
fi

# reassemble pages

cd /home/gemini/gemini/git/tr-git # to be sure
	# git pull
echo "# Latest Git Commits" > ~/gemini/git/change/index.gmi

echo "\n## Latest Change" >> ~/gemini/git/change/index.gmi
echo "\n\`\`\`" >> ~/gemini/git/change/index.gmi
git show --format='--8<---[ %H ] | [ %aD ]----' * >> ~/gemini/git/change/index.gmi
echo "\`\`\`\n" >> ~/gemini/git/change/index.gmi

echo "## Two Changes Ago" >> ~/gemini/git/change/index.gmi
echo "\n\`\`\`" >> ~/gemini/git/change/index.gmi
git show HEAD~2 --format='--8<---[ %H ] | [ %aD ]----' * >> ~/gemini/git/change/index.gmi
echo "\`\`\`\n" >> ~/gemini/git/change/index.gmi

echo "## Three Changes Ago" >> ~/gemini/git/change/index.gmi
echo "\n\`\`\`" >> ~/gemini/git/change/index.gmi
git show HEAD~3 --format='--8<---[ %H ] | [ %aD ]----' * >> ~/gemini/git/change/index.gmi
echo "\`\`\`\n" >> ~/gemini/git/change/index.gmi

echo "## Four Changes Ago" >> ~/gemini/git/change/index.gmi
echo "\n\`\`\`" >> ~/gemini/git/change/index.gmi
git show HEAD~4 --format='--8<---[ %H ] | [ %aD ]----' * >> ~/gemini/git/change/index.gmi
echo "\`\`\`\n" >> ~/gemini/git/change/index.gmi

echo "## Five Changes Ago" >> ~/gemini/git/change/index.gmi
echo "\n\`\`\`" >> ~/gemini/git/change/index.gmi
git show HEAD~5 --format='--8<---[ %H ] | [ %aD ]----' * >> ~/gemini/git/change/index.gmi
echo "\`\`\`\n" >> ~/gemini/git/change/index.gmi

echo "\n# Files changed in the past week" >> ~/gemini/git/change/index.gmi
echo "\n\`\`\`" >> ~/gemini/git/change/index.gmi
git diff --stat @{7.days.ago}  >> ~/gemini/git/change/index.gmi
echo "\`\`\`\n" >> ~/gemini/git/change/index.gmi

echo "\n# Older Commits (Latest Shown First)" >> ~/gemini/git/change/index.gmi
echo "\n\`\`\`" >> ~/gemini/git/change/index.gmi
git log --all --decorate --oneline --graph >> ~/gemini/git/change/index.gmi
echo "\`\`\`\n" >> ~/gemini/git/change/index.gmi
echo "=>      /        back to Techrights (Main Index)"  >> ~/gemini/git/change/index.gmi

echo "# List of Files (Self-Hosted Git)" > ~/gemini/git/files/index.gmi
echo "\`\`\`" >> ~/gemini/git/files/index.gmi
git ls-tree --full-tree -r --name-only HEAD >> ~/gemini/git/files/index.gmi
echo "\`\`\`\n" >> ~/gemini/git/files/index.gmi
echo "=>      /        back to Techrights (Main Index)"  >> ~/gemini/git/files/index.gmi

echo "\n# Git browser\n" > Desktop-Utils/index.gmi 
echo "=> /git/ Back to index\n" >> Desktop-Utils/index.gmi 
find  Desktop-Utils/ -type f -not -name index.gmi \
	-printf '## %p\n\n```\n' \
	-exec cat {} \; \
	-printf '\n```\n'\
	>> Desktop-Utils/index.gmi

echo "# Git browser"\n > Gemini/index.gmi 
echo "=> /git/ Back to index\n" >> Gemini/index.gmi 
find  Gemini/ -type f -not -name index.gmi \
	-printf '## %p\n\n```\n' \
	-exec cat {} \; \
	-printf '\n```\n'\
	>> Gemini/index.gmi

echo "# Git browser"\n > IPFS/index.gmi 
echo "=> /git/ Back to index\n" >> IPFS/index.gmi 
find  IPFS/ -type f -not -name index.gmi \
	-printf '## %p\n\n```\n' \
	-exec cat {} \; \
	-printf '\n```\n' \
	>> IPFS/index.gmi

echo "# Git browser"\n > IRC/index.gmi 
echo "=> /git/ Back to index\n" >> IRC/index.gmi 
find  IRC/ -type f -not -name index.gmi \
	-printf '## %p\n\n```\n' \
	-exec cat {} \; \
	-printf '\n```\n'\
	>> IRC/index.gmi

echo "# Git browser"\n > Links/index.gmi 
echo "=> /git/ Back to index\n" >> Links/index.gmi 
find  Links/ -type f -not -name index.gmi \
	-printf '## %p\n\n```\n' \
	-exec cat {} \; \
	-printf '\n```\n'\
	>> Links/index.gmi

echo "# Git browser"\n > sbin/index.gmi 
echo "=> /git/ Back to index\n" >> sbin/index.gmi 
find  sbin/ -type f -not -name index.gmi \
	-printf '## %p\n\n```\n' \
	-exec cat {} \; \
	-printf '\n```\n'\
	>> sbin/index.gmi

echo "# Git browser"\n > Unit-files/index.gmi 
echo "=> /git/ Back to index\n" >> Unit-files/index.gmi 
find  Unit-files/ -type f -not -name index.gmi \
	-printf '## %p\n\n```\n' \
	-exec cat {} \; \
	-printf '\n```\n'\
	>> Unit-files/index.gmi

echo "# Git browser"\n > sbin-tm/index.gmi 
echo "=> /git/ Back to index\n" >> sbin-tm/index.gmi 
find  sbin-tm/ -type f -not -name index.gmi \
	-printf '## %p\n\n```\n' \
	-exec cat {} \; \
	-printf '\n```\n'\
	>> sbin-tm/index.gmi

exit 0

Gemini/gemini-get-cosmo.sh

#!/usr/bin/sh
# SHELL=/usr/bin/sh
# export PATH=$PATH:/home/gemini/bin

# openssl s_client -quiet -crlf   \
#    -servername skyjake.fi   \
#    -connect skyjake.fi:1965 \
#  | awk '{ print "response: " $0 }'
#gemini://skyjake.fi/~Cosmos/

#openssl s_client -quiet -crlf   \
#-servername skyjake.fi   \
#-connect skyjake.fi:1965 \
#| awk '{ print "response: " $0 }'
#gemini://skyjake.fi/~Cosmos/

openssl s_client -quiet -crlf   \
-servername skyjake.fi   \
-connect skyjake.fi:1965 \
| awk '{ print "" $0 }' > /tmp/cosmos
gemini://skyjake.fi/~Cosmos/

Gemini/gemini-get-and-convert-wiki-get-rss.pl

#!/usr/bin/perl

# 2021-07-03

# read a hard-coded RSS feed and extract the URLs for changed
# wikipages, if they were changed on or after the designated date

use utf8;
use Getopt::Std;
use Time::ParseDate;
use Time::Piece;
use XML::Feed;
use URI;

use strict;
use warnings;

my $script = $0;
our $VERBOSE = 0;

# work-arounds for 'wide character' error from wrong UTF8
binmode(STDIN,  ":encoding(utf8)");
binmode(STDOUT, ":encoding(utf8)");

our %opt;
getopts('d:huv', \%opt);

if (defined($opt{'h'})) {
    &usage($script);
}

if (defined($opt{'v'})) {
    $VERBOSE++;
}

my $url ="http://techrights.org/wiki/index.php?title=Special:RecentChanges&feed=rss";

my $utc = 0;    # treat input as a local time and convert to UTC
if (defined($opt{'u'})) {
    $utc = 1;   # treat input as UTC without conversion
}

my $sdts;
if (defined($opt{'d'})) {
    $sdts = parsedate($opt{'d'}, GMT=>$utc);
} else {
    $sdts = parsedate('yesterday');
}

print STDERR qq(S=$sdts\n)
 if ($VERBOSE);

my $t = Time::Piece->strptime($sdts, '%s');

print STDERR qq(D=),$t->strftime("%a, %d %b %Y %H:%M:%S %Z"),qq(\n)
    if ($VERBOSE);

my @urls = &get_feed_urls($t, $url);

print join("\n", @urls),qq(\n);

exit(0);

sub usage {
    my ($script) = (@_);
    $script =~ s/^.*\///;

    print <parse(URI->new($uri));
    };

    if ($@) {
        print STDERR $@,qq(\n);
        print STDERR qq(  Failed feed for '$uri'\n);
        return(0);
    }

    my $count = 0;
    foreach my $entry ($feed->entries) {
        # print STDERR Dumper($entry),qq(\n\n)
        #    if($VERBOSE);

        # entry time
        my $ft = $entry->{entry}{pubDate}
            || $entry->issued
            || $entry->modified;

	# entry time in seconds
        my $et = parsedate($ft) || 0;

        next unless($et =~ /^\d+$/ && $et >= $t );


	my $link = URI->new($entry->link);

	my %keywords = $link->query_form();

	my $new_uri = URI->new();
	$new_uri->scheme($link->scheme);
	$new_uri->host($link->host);
	$new_uri->path($link->path);
	$new_uri->query('title='.$keywords{'title'});
	push(@urls, $new_uri->canonical) if (defined($link));
    }

    @urls = &unique(@urls);

    return(@urls)
}

sub unique {
    my (@urls) = (@_);

    my %link;
    foreach my $u (@urls) {
	$link{$u}++;
    }
    my @links=();
    foreach my $l (sort keys %link) {
	push(@links, $l);
    }

    return(@links);
}

Gemini/tr-gemini-git-update.sh

#!/bin/sh

# 2021-09-13
# update read-only git archive

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

# set -x
set -e

documentroot=/home/gemini/techrights.org

test -d ${documentroot}/git
test -w ${documentroot}/git

if test -d ${documentroot}/git/tr-git; then
	cd ${documentroot}/git/tr-git/
        git fetch --all
        git reset --hard
        git pull
else
	cd ${documentroot}/git/
	git clone /home/git/tr-git/
	git pull
fi

# reassemble pages

cd ${documentroot}/git/tr-git # to be sure
	# git pull
echo "# Latest Git Commits" > ${documentroot}/git/change/index.gmi

echo "\n## Latest Change" >> ${documentroot}/git/change/index.gmi
echo "\n\`\`\`" >> ${documentroot}/git/change/index.gmi
git show --format='--8<---[ %H ] | [ %aD ]----' * >> ${documentroot}/git/change/index.gmi
echo "\`\`\`\n" >> ${documentroot}/git/change/index.gmi

echo "## Two Changes Ago" >> ${documentroot}/git/change/index.gmi


c=1
while [ $c -lt 5 ]; do
	echo "\n\`\`\`" \
		>> ${documentroot}/git/change/index.gmi
	git show HEAD~1 \
		--format='--8<---[ %H ] | [ %aD ]----' * \
		>> ${documentroot}/git/change/index.gmi
	echo "\`\`\`\n" \
		>> ${documentroot}/git/change/index.gmi
	c=$(($c + 1))
done

echo "\n# Files changed in the past week" \
	>> ${documentroot}/git/change/index.gmi
echo "\n\`\`\`" \
	>> ${documentroot}/git/change/index.gmi
git diff --stat @{7.days.ago} \
	>> ${documentroot}/git/change/index.gmi
echo "\`\`\`\n" \
	>> ${documentroot}/git/change/index.gmi
echo "\n=>      /        back to Techrights (Main Index)" \
	>> ${documentroot}/git/change/index.gmi

echo "\n# Older Commits (Latest Shown First)" \
	> ${documentroot}/git/all_changes/index.gmi
echo "\n\`\`\`" \
	>> ${documentroot}/git/all_changes/index.gmi
git log --all --decorate --stat --graph \
	| grep -v 'Author:' \
		>> ${documentroot}/git/all_changes/index.gmi
echo "\`\`\`\n" \
	>> ${documentroot}/git/all_changes/index.gmi
echo "\n=>      /        back to Techrights (Main Index)" \
	>> ${documentroot}/git/all_changes/index.gmi

echo "# List of Files (Self-Hosted Git)" \
	> ${documentroot}/git/files/index.gmi
echo "\`\`\`" \
	>> ${documentroot}/git/files/index.gmi
git ls-tree --full-tree -r --name-only HEAD \
	>> ${documentroot}/git/files/index.gmi
echo "\`\`\`\n" \
	>> ${documentroot}/git/files/index.gmi
echo "=>      /        back to Techrights (Main Index)" \
	>> ${documentroot}/git/files/index.gmi

for d in */ ; do
    echo "# Git browser: $d" > "$d"/index.gmi
    echo "This page presents code associated with the module/unit named above." >> "$d"/index.gmi
    echo "=> /git/all_changes/ Summary of changes" >> "$d"/index.gmi
    echo "=> /git/ Back to Git index" >> "$d"/index.gmi
    echo "=> /git/agplv3/ Licence (AGPLv3)" >> "$d"/index.gmi
    find  "$d" -type f -not -name index.gmi \
                -printf '## %p\n\n```\n' \
                -exec cat {} \; \
                -printf '\n```\n'\
                >> "$d"index.gmi
    echo "=> / Back to main index" >> "$d"/index.gmi
    test -d "$d"/changes/ || mkdir -p "$d"/changes/
    echo "Latest changes in $d (latest first)" > "$d"/changes/index.gmi
    echo "=> /git/ Back to Git index" >> "$d"/changes/index.gmi

    git log "$d" \
        | sed -e '/Author/d' \
            -e 's/commit /# Commit identifier /' \
            -e 's/Date:/##Time of change: /' \
        >> "$d"/changes/index.gmi

    echo "=> / Back to main index" >> "$d"/changes/index.gmi
done

exit 0

Gemini/monitor-gemini.sh

#!/bin/sh

PATH=/home/gemini/bin:/usr/local/bin:/usr/bin:/bin

amfora= '~/amfora_1.7.2_linux_64-bit'

while true;
do
        $amfora gemini.techrights.org &
        p=$!
        sleep 120

         $amfora simplynews.metalune.xyz/theguardian.com &
        p=$(echo "$! $p")
        sleep 120

        $amfora gemini://gemini.bortzmeyer.org/software/lupa/stats.gmi &
        p=$(echo "$! $p")
        sleep 120

        kill $p
done

exit 0

Gemini/gemini-make-feed.pl

#!/usr/bin/perl

# read TR's gemtext files and guess at building an RSS feed from them

use Getopt::Std;
use File::Find;
use Date::Parse;
use Date::Calc qw( Add_Delta_Days Gmtime );
use Time::Local;
use XML::Feed;
use DateTime;

use English;
use strict;
use warnings;

our %opt;

getopts('d:ho:r:v', \%opt);

my $script = $0;
$script =~ s|^.*\/||;

&usage($script) if ($opt{'h'});

my $path = shift;

if (! defined($path) || ! -d $path) {
    &usage($script, 1);
}

our $date;
if ($opt{'d'}) {
    $date = str2time($opt{'d'}, 'UTC') || &usage($script, 2);
} else {
    print "Date missing.  Assuming yesterday.\n";
    my ($year,$month,$day) = Gmtime();
    ($year,$month,$day) = Add_Delta_Days($year,$month,$day, -1);
    print "$year, $month, $day\n";
    $month = $month - 1;
    $year = $year - 1900;
    $date = timegm( 0, 0, 0, $day, $month, $year)
}

if (! defined($opt{'r'}) && ! -d $opt{'r'}) {
    &usage($script, 4);
}

my ($output);

if (defined($opt{'o'})) {
    # XXX needs proper sanity checking for path and filename at least
    $output = $opt{'o'};
    $output =~ s/[\0-\x1f]//g;
    if ($output =~ /^([-\/\w\.]+)$/) {
        $output = $1;
    } else {
        die("Bad path or file name: '$output'\n");
    }
} else {
    $output = '/dev/stdout';
}

our @retrieved_files;
File::Find::find({wanted => \&wanted}, $path);

my @files = @retrieved_files;

if ($opt{'v'}) {
    print "Files:\n", join("\n",@files),"\n";
}

my %metadata = &read_files(@files);

my $feed = XML::Feed->new('RSS');
$feed->title('Techrights Gemini Feed');
$feed->modified(DateTime->now(time_zone=> 'UTC'));
$feed->link('gemini://gemini.techrights.org/feed.rss');
$feed->language('en');
$feed->description('Techrights Gemini feed');
# $feed->ttl(1440);

for my $f (sort keys %metadata) {
    my $r = "^$opt{'r'}";
    my $p = $f;
    my $u = "gemini://gemini.techrights.org/";
    $p =~ s/$r/$u/;
    $p =~ s|(?from_epoch(epoch=>$time, time_zone=>"UTC");
    $title =~ s/^[#\s●]+//;

    my $entry = XML::Feed::Entry->new();
    $entry->link($p);
    $entry->title($title);
    $entry->modified($time);
    $feed->add_entry($entry);
}

if ($output eq '/dev/stdout') {
    print $feed->as_xml;
} elsif ($output) {
    open(my $out, ">", $output)
            or die("Could not open '$output' for writing: $!\n");
        binmode($out, ':encoding(utf8)');
    print $out $feed->as_xml;
    close($out);
}

exit(0);

sub usage {
    my ($script, $reason) = (@_);

    if ($reason == 1 ) {
	print "Missing path to GemText files\n";
    } elsif ($reason == 2 ) {
	print "Bad date or date format.\n";
    } elsif ($reason == 3) {
	print "Date missing\n";
    } elsif ($reason == 4) {
	print "Document root (base) missing\n";
	print "use the -r option to set it\n";
    }
    print "\n";
    print "Usage: \n";
    print "$script [options] -r documentroot  path\n";
    print " -d starting date, defaults to yesterday\n";
    print " -o path to output file, defaults to stdout\n";
    print " -r document root for the Gemini server. required\n";
    print " -v verbose output\n";
    print " -h help - this output\n";

    exit(0);
}

sub wanted {
    my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
	$atime,$mtime,$ctime,$blksize,$blocks);

    # print "D=$File::Find::name\n";
    if ((($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
     $atime,$mtime,$ctime,$blksize,$blocks) = lstat($_)) &&
	-f $File::Find::name &&
	m/\.gmi$/ &&
	$mtime >= $date) {
	push(@retrieved_files, $File::Find::name);
	# print"$File::Find::name\n";
    }
}

sub read_files {
    my (@files) = (@files);
    my %md =();
    for my $file (@files) {
	open(my $fh, "<", $file)
	    or die("Could not open '$file' : $!\n");
	my $title = '';
	my $count = 0;
	while (my $line = <$fh>) {
	    chomp($line);
	    if ($line =~ m/^#/ && $count++) {
		my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,
		    $size,$atime,$mtime) = stat($fh);
		$title = $line;
		$md{$file}{'title'} = $title;
		$md{$file}{'time'} = $mtime;
		last;
	    }
	}
	close($fh);
    }

    return(%md);
}

Gemini/show-new-visitors-count.sh

#!/bin/sh
#
# Initial edits 2021-02-27
# Improvements for scaling 2021-09-09 (RS)

PATH=/usr/bin:/bin:/home/gemini/bin
capsule='/home/gemini'
RED='\033[0;31m' # Set some colours to help separate output visually
AMBER='\033[0;33m'
YELLOW='\033[1;33m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
NC='\033[0m' # No color
alertthreshold=1000 # How many requests per 10 minutes (by default) are considered 'too much'
dt=$(date +"%F %H:%M %Z")	# now
d=$(date -d "$dt" +"%F")	# today
dt=$(date -d "$dt") 		# reformat now

lockfile="/tmp/lockfile-$(basename $0)"
trap "rm -f -- $lockfile;" EXIT

if [ "${FLOCK}" != "$lockfile" ]; then
	if flock -en "$lockfile" -c date; then
		exec env FLOCK="$lockfile" flock -en "$lockfile" "$0" "$@"
	else
		echo "Already Locked. Aborting." >&2
		exit 1
	fi
else
	echo Locking
fi

echo '             🅶🅴🅼🅸🅽🅸 🅳🅳🅾🆂 🅿🆁🅾🆃🅴🅲🆃🅸🅾🅽' # Yup, just DDOS Protection (alert and response)

printf "${GREEN}"
systemctl status agate | grep Active
printf "${NC}\n"

printf "${AMBER}██████████████████████${NC} TOP REQUESTS ${AMBER}█████████████████████████████████████████▉▊▋${NC}\n"

set -e
# limit to just latest 1,000 in each file as logs have grown very large
tail -n1000 $capsule/logs/gemini-log-* \
	| awk '$3 {a[$3]++} END{ for (b in a) {print a[b],b}}' OFS="\t" \
	| sort -k1,1n -k2,2 | tail -n6

printf "${RED}██████████████████████${NC} LATEST 100 ${RED}███████████████████████████████████████████▉▊▋${NC}\n"

tail -n100 $capsule/logs/gemini-log-$d.log \
	| awk '$3 {a[$3]++} END{ for (b in a) {print a[b],b}}' OFS="\t" \
        | sort -k1,1n -k2,2 | tail -n4

TOTAL=$(wc -l < $capsule/logs/gemini-log-$d.log)
LASTTOTAL=$(cat $capsule/last-total.txt)

sum=$(( $TOTAL - $LASTTOTAL )) # Get the diff
if [ $sum -gt $alertthreshold ] # If too drastic an increase
	then
        printf "${RED}"
	echo -n $sum
	echo " new requests in 10 minutes. Check logs." # Verbose
	printf "${NC}\n"
	echo ''
	echo " Consider running: sudo iptables -I INPUT -i wlan0 -s {OFFENDER_IP}/24 -j DROP" # Only suggest, do not run (yet)
	echo ${TOTAL} >  $capsule/last-total.txt # Update tally so as to not suppress the next run
	printf "${RED}"
	cat $capsule/logo.txt
	printf "${NC}\n"
        exit 2  # Sound the system bell
fi

echo ${TOTAL} >  $capsule/last-total.txt


# FOR ALL DAYS:
# HOSTS=$(cat $capsule/logs/gemini-log-* | awk '$3 {a[$3]++} END{ for (b in a) {print a[b],b}}' OFS="\t" \
#        | sort -k1,1n -k2,2 | wc -l)
# For current day only
HOSTS=$(cat $capsule/logs/gemini-log-$d.log \
	| awk '$3 {a[$3]++} END{ for (b in a) {print a[b],b}}' OFS="\t" \
        | sort -k1,1n -k2,2 | wc -l)
# HOSTS=off # turn off the above for extra speed


printf "${YELLOW}█████████████${NC} ${TOTAL} total requests and ${HOSTS} known hosts ${YELLOW}█████████████████▉▊▋${NC}\n"

echo -n $sum
echo " requests since last run  (usually 10 minutes ago)."
echo -n 'Alert threshold (system bell): '
echo -n $alertthreshold
echo ' or higher'

# printf "${RED}____________________________________________________________${NC}\n"
echo
printf "                   ${GREEN}Latest 20 requests${NC} (updated <10 mins ago)\n\n"
tail -n20 $capsule/logs/gemini-log-$d.log \
	| sed -E -e 's/[^ ]+ //; s/ /  ⃪ /;'
echo _________________________________________________________
printf "${BLUE}"
cat $capsule/logo.txt
echo _________________________________________________________
# wc -l $capsule/logs/gemini-log-* | cut -b1-7,38-47
# wc -l $capsule/logs/gemini-log-* | cut -d/  -f1,5- | sed 's/ \/gemini-log//' | sed 's/.log//'| sed s/'-'/' reqs '/ | sed s/'2021-'/' | 2021-'/ | sed s/'-'/' \/ '/ | sed s/'-'/' \/ '/
wc -l $capsule/logs/gemini-log-* \
	| sed -e 's#/.*-log-#reqs | #;
		s#-# / #g;
		s#\.log$##;' \

printf "${NC}\n"

Gemini=$(wc -l $capsule/logs/gemini-* | tail -n1)
Pages=$(find $capsule/gemini -name '*.gmi' | wc -l)
Gemactive=$(/usr/sbin/service agate status | grep Active)

echo "# Techrights Gemini Capsule Stats" >   $capsule/gemini/stats/index.gmi
echo "## Gemini requests since start of month: $Gemini " >>   $capsule/gemini/stats/index.gmi
echo "## Total pages in capsule: $Pages " >> $capsule/gemini/stats/index.gmi
echo "## Uptime (daemon): $Gemactive "  >>   $capsule/gemini/stats/index.gmi  # create page
echo "## Top accessed pages yesterday (highest number first):\n" >>   $capsule/gemini/stats/index.gmi
cat $capsule/logs/top-yesterday.log  >>   $capsule/gemini/stats/index.gmi
echo "## Detailed (number of pages, by date) \n • Please note that all logs are deleted at end of each month (completely, permanently)"  >>   $capsule/gemini/stats/index.gmi
echo "" >>   $capsule/gemini/stats/index.gmi
echo " Page requests ── Date"  >>   $capsule/gemini/stats/index.gmi
echo "   ↧ ────────────── ↧" >>   $capsule/gemini/stats/index.gmi
echo "┌────────────────────────────────┐"  >>   $capsule/gemini/stats/index.gmi
# wc -l $capsule/logs/gemini-log-* | cut -b1-8,39-47 >> $capsule/gemini/stats/index.gmi # send to capsule too
# wc -l $capsule/logs/gemini-log-* | cut -d/  -f0,5- | sed 's/ \/gemini-log//' | sed 's/.log//'| sed s/'-'/' reqs '/ | sed s/'2021-'/' | 2021-'/ | sed s/'-'/' \/ '/ | sed s/'-'/' \/ '/ >> $capsule/gemini/stats/index.gmi
wc -l $capsule/logs/gemini-log-* \
	| sed -e 's#/.*-log-#reqs | #; s#-# / #g; s#\.log$##;' \
>> $capsule/gemini/stats/index.gmi

echo " ──────────────────────────────── " >>   $capsule/gemini/stats/index.gmi

echo '## Daily Stats Archive' >> $capsule/gemini/stats/index.gmi

# Each year another block will need to be added below for the past years

y=$(date -d "$d -1 year" +"%Y")

echo "### $y Archive" >> $capsule/gemini/stats/index.gmi
echo "=> $y.gmi All days ($y)" >> $capsule/gemini/stats/index.gmi
echo "# $y Stats Archive" > $capsule/gemini/stats/$y.gmi

find /home/gemini/gemini/stats/ \
    -type f \
    -name "stats-$y*.gmi" \
    -exec basename {} \; \
    | sort \
    | sed -r -e 's/([0-9]{4}-[0-9]{2}-[0-9]{2})\.gmi/\1.gmi \1/;   s/^/=> /' \
    -e '/01-01.*/i ### January ㋀' \
    -e '/02-01.*/i ### February ㋁' \
    -e '/03-01.*/i ### March ㋂' \
    -e '/04-01.*/i ### April ㋃' \
    -e '/05-01.*/i ### May ㋄' \
    -e '/06-01.*/i ### June ㋅' \
    -e '/07-01.*/i ### July ㋆' \
    -e '/08-01.*/i ### August ㋇' \
    -e '/09-01.*/i ### September ㋈' \
    -e '/10-01.*/i ### October ㋉' \
    -e '/11-01.*/i ### November ㋊' \
    -e '/12-01.*/i ### December ㋋' \
    >> $capsule/gemini/stats/$y.gmi

y=$(date -d "$d -2 year" +"%Y")

echo "### $y Archive" >> $capsule/gemini/stats/index.gmi
echo "=> $y.gmi All days ($y)" >> $capsule/gemini/stats/index.gmi
echo "# $y Stats Archive" > $capsule/gemini/stats/$y.gmi

find /home/gemini/gemini/stats/ \
    -type f \
    -name "stats-$y*.gmi" \
    -exec basename {} \; \
    | sort \
    | sed -r -e 's/([0-9]{4}-[0-9]{2}-[0-9]{2})\.gmi/\1.gmi \1/;   s/^/=> /' \
    -e '/01-01.*/i ### January ㋀' \
    -e '/02-01.*/i ### February ㋁' \
    -e '/03-01.*/i ### March ㋂' \
    -e '/04-01.*/i ### April ㋃' \
    -e '/05-01.*/i ### May ㋄' \
    -e '/06-01.*/i ### June ㋅' \
    -e '/07-01.*/i ### July ㋆' \
    -e '/08-01.*/i ### August ㋇' \
    -e '/09-01.*/i ### September ㋈' \
    -e '/10-01.*/i ### October ㋉' \
    -e '/11-01.*/i ### November ㋊' \
    -e '/12-01.*/i ### December ㋋' \
    >> $capsule/gemini/stats/$y.gmi

echo '### This Year' >> $capsule/gemini/stats/index.gmi

y=$(date -d "$d" +"%Y")

find /home/gemini/gemini/stats/ \
    -type f \
    -name "stats-$y*.gmi" \
    -exec basename {} \; \
    | sort \
    | sed -r -e 's/([0-9]{4}-[0-9]{2}-[0-9]{2})\.gmi/\1.gmi \1/;   s/^/=> /' \
    -e '/01-01.*/i ### January ㋀' \
    -e '/02-01.*/i ### February ㋁' \
    -e '/03-01.*/i ### March ㋂' \
    -e '/04-01.*/i ### April ㋃' \
    -e '/05-01.*/i ### May ㋄' \
    -e '/06-01.*/i ### June ㋅' \
    -e '/07-01.*/i ### July ㋆' \
    -e '/08-01.*/i ### August ㋇' \
    -e '/09-01.*/i ### September ㋈' \
    -e '/10-01.*/i ### October ㋉' \
    -e '/11-01.*/i ### November ㋊' \
    -e '/12-01.*/i ### December ㋋' \
    >> $capsule/gemini/stats/index.gmi

echo '• No personally-identifying data is in these pages. The raw logs get deleted.' >> $capsule/gemini/stats/index.gmi

echo " ──────────────────────────────── \n \n=>      /        back to Techrights (Main Index)" >>   $capsule/gemini/stats/index.gmi
echo -n "🅶🅴🅼🅸🅽🅸 Page last updated at " >>   $capsule/gemini/stats/index.gmi
echo $dt >>   $capsule/gemini/stats/index.gmi

wget --quiet --tries=22 --retry-on-http-error=500,503 \
    -O $capsule/gemini/chat/irc.gmi http://techrights.org/irc/log.gmi \
	&& sleep 6 \
	&& mv --backup -f \
		$capsule/gemini/chat/irc.gmi $capsule/gemini/chat/index.gmi \
	|| gemini-ping-roy.sh bright

# cat $capsule/techrights-logo-colour.txt

echo Unlocking
exit 0

Gemini/archives.gmi

Welcome to Techrights Archives, static section of the capsule

=> / Back to index

# Techrights Archives

## Popular Sections

=> /epo/ EPO Articles ▤
=> /linuxfoundation/ Linux Foundation ▤
=> /gatesfoundation/ Gates Foundation ▤
=> /wiki/ Techrights wiki ▤
=> /chat/ Latest chat logs for #techrights (updated every 10 minutes) ▤

# Full Archives

=> /archive/ Monthly Archives of Articles
=> /videos/ Techrights videos ◉
=> stats/index.gmi Statistics for this month 🗠
=> /git/ Techrights git G

## Techrights IRC Archives

=> irc/index.gmi ㏒ - IRC logs for #techrights
=> social/index.gmi ㏒ - IRC logs for #boycottnovell-social
=> techbytes/index.gmi ㏒ - IRC logs for #techbytes
=> boycottnovell/index.gmi ㏒ - IRC logs for #boycottnovell

## Techrights Site Archives

=> /ipfs/ IPFS Archive 🗜
=> /bulletins/ Techrights Bulletins 🖃

 • Plain Text Bulletins are included, but may be hard to get over IPFS due to their size
 • The World Wide Web 🗺  site always contains the latest Techrights bulletins

## Techrights Syndication

=> /feed.xml RSS/XML ♲
=> /planet/ Planet Gemini ♲
=> /feed/ Feed as GemText ♲
=> /daily-feed/ Daily Feeds ♲

=> /logo/logo.png Site logo (if your Gemini client supports that)

### ➮ Sharing is caring. Content is available under CC-BY-SA. ⟲

Gemini/gemini-get-and-convert-wiki.pl

#!/usr/bin/perl -T

# 2021-07-05

# main script to launch two helper scripts:
# a) gemini-get-and-convert-wiki-get-rss.pl - read URLs from RSS
# b) gemini-get-and-convert-wiki-from-html.pl - convert from specific HTML to Gemini
# then feed the output from a into b and save as a file

use Time::ParseDate;
use Date::Calc qw(Localtime);
use URI;
use File::Path qw(make_path);
use Getopt::Std;
use URI::Escape;
use utf8;

use warnings;
use strict;

our %opt;
getopts('d:hUv', \%opt);

my $date = $opt{'d'} || 'now -2 days';
$date = &iso_8601_date($date);
die() unless($date);
my $destination = '/home/gemini/gemini/wiki';

$ENV{'PATH'} = '/home/gemini/bin:/usr/local/bin:/usr/bin:/bin';

my @urls = &read_rss_feed($date);

foreach my $url (@urls) {
    my $uri = URI->new($url)->canonical;
    my %query = $uri->query_form();
    my $title = $query{'title'};

    next if ($title =~ m/\.\./);

    my $outdir = join('/',$destination,$title);
    print qq(outdir = $outdir\n) if $opt{'v'};

    &convert_html_to_gemini($uri, $outdir);
}

&write_index unless($opt{'U'});

exit(0);

sub iso_8601_date {
    my ($date) = (@_);

    my $seconds = parsedate($date);
    my ($year,$month,$day) = Localtime($seconds);
    $date = sprintf("%04d-%02d-%02d", $year,$month,$day);
    return($date);
}

sub read_rss_feed {
    my ($date) = (@_);

    my $feed_source = "gemini-get-and-convert-wiki-get-rss.pl";

    next unless($date =~ m/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/);

    print qq(Date = $date\n) if ($opt{'v'});
    open(my $fh, "-|", $feed_source, "-d", $date)
	or die("Could not open feed source \"$feed_source\": $!\n");

    my @urls;
    while (my $url = <$fh>) {
	chomp($url);
	print qq(URL = $url\n) if ($opt{'v'});
	push(@urls, $url);
    }

    close($fh);
    return(@urls);
}

sub convert_html_to_gemini {
    my ($uri, $outdir) = (@_);
    unless (-e $outdir) {
	make_path($outdir, {mode=>0755});
    }

    unless (-w $outdir) {
	print STDERR qq(Cannot write to "$outdir"\n);
	return(0);
    }

    ($uri) = uri_unescape($uri);

    my $outfile = join('/', $outdir, 'index.gmi');
    #    ($outfile) =~ ($outfile =~ m/^([^\x00-\x20]+)$/g);

    print qq(\tO = $outfile\n) if ($opt{'v'});

    my @doc = ();

    $uri = uri_escape($uri,"\x20\x22\x27\x60");
    ($uri) = ($uri =~ m/^([^\x00-\x20]+)$/);

    open(my $html, '-|', "curl", "-s", "$uri")
	or die("Cannot open 'curl'\n");
    while(my $line = <$html>) {
	push(@doc, $line);
    }
    close($html);

    print qq(\tOO = $outfile\n) if($opt{'v'});

    open($html,'|-',"gemini-get-and-convert-wiki-from-html.pl",
	"-w", "$outfile", "-")
	or die("Cannot open converter\n");

    foreach my $line (@doc) {
	print $html $line;
    }
    close($html);

    return(1);
}

sub write_index {

    my $path = '/home/gemini/gemini/wiki';
    my $base = '/wiki';

    opendir(my $dir, $path)
	or die("Could not read '$path': $!\n");

    my @dirs=();
    while (readdir($dir)) {
	my $d = $_;
	next unless(-d "$path/$d");
	next if ($d =~ m/^\.+/);
	push(@dirs, $_);
    }
    closedir($dir);

    open(my $o, ">", "$path/index.gmi")
	or die("Could not open \"$path/index.gmi\" for writing: $!\n");

    print $o qq(# Techrights Wiki mirror\n\n);
    print $o qq(=>\tgemini://gemini.techrights.org/\tTechrights\n\n);

    foreach my $d (sort @dirs) {
	my $dd = $d;
	$dd =~ s/_/ /g;
	print $o qq(=>\t$base/$d/\t$dd\n);
    }

    print $o qq(\n### ➮ Sharing is caring. );
    print $o qq(Content is available under CC-BY-SA. ⟲ \n);

    close($o);
}

Gemini/export-stats.sh

#/bin/sh

# 2021-09-15
# runs as 'pi'

PATH=/usr/bin:/bin

awk '
        $5~/^agate/ {
                if($11~/favicon\.txt/) {
                        next;
                }
                sub(/^"/,"",$11);
                sub(/"$/,"",$11);
                sub(/index\.gmi$/,"",$11);
                sub("^gemini://gemini.techrights.org:1965/",
                        "gemini://gemini.techrights.org/",$11);
                if($11!~/\/$/) {
                        $11=$11 "/";
                }
                if($11=="/") {
                        $11="gemini://gemini.techrights.org/";
                }
                a[$11]++
                c++
        }

        END {
                print(c " Unique Requests\n");
                for(u in a) {
                        print(a[u],u)
                }
        }' OFS="\t" /var/log/syslog.1 \
| sort -k1,1nr -k2,2 \
| head -n 31 \
| awk ' NR==2 {
                print("\nRequests\tPage")
        }
        NR>1 {
                print("=> ", $2, $0 " ⇛")
        }
        NR<2 {
                print;
        }
' OFS="\t" \
> /home/gemini/logs/top-yesterday.log

echo -n 'Accessed over HTTP/S gateway: '>> /home/gemini/logs/top-yesterday.log
wc -l  /var/log/nginx/access.log.1 | cut -f 1 -d ' ' >> /home/gemini/logs/top-yesterday.log

exit 0

Gemini/tr-gemini-latest-videos.pl

#!/usr/bin/perl

# 2021-09-17
# update latest video gallery page

use HTML::TreeBuilder::XPath;
use File::Basename;
use JSON;

print "# Latest Techrights Videos\n\n";
print "Most recent shown first (the list below is limited to past 7 days)\n\n";

my %videos   = &xhtml_video_links('-');
my %metadata = &fetch_metadata(%videos);

foreach my $key (sort {$a<=>$b} keys %metadata) {
    print "=> ";
    print $metadata{$key}{'link'}," ";

    print "↺ ";
    printf ("%2d ",$key);
    if (exists($metadata{$key}{'title'})) {
	print $metadata{$key}{'title'};
	if(exists($metadata{$key}{'date'})){
	    print " (",$metadata{$key}{'date'},")";
	}
    } else {
	print $metadata{$key}{'file'};
    }
    print "\n";
}

print "\n=> /videos/ Show videos archive\n";
print "=> / Back to homepage\n";

exit(0);

sub xhtml_video_links{
    my ($file)= (@_);

    my $is_stdin = 0;
    my $input;

    if ($file eq '-') {
           $input = *STDIN;
           $is_stdin++;
    } else {
           # force input to be read in as UTF-8
           open ($input, "<:utf8", $file)
           or die("Could not open file '$file' : error: $!\n");
    }

    my $xhtml = HTML::TreeBuilder::XPath->new;
    $xhtml->implicit_tags(1);
    $xhtml->no_space_compacting(0);

    $xhtml->parse_file($input)
    or die("Could not parse '$file' : $!\n");

    my %videos = ();
    my $listpos = 0; # reset position at 0
    for my $l ($xhtml->findnodes_as_strings('//a[@target="video"]/@href') ) {
           my $link = qq(http://techrights.org/videos/).$l;
           my $vid  = basename($link);
           $listpos++;
           $videos{$listpos}{'link'} = $link;
	   $videos{$listpos}{'file'} = $vid;
    }
    $xhtml->destroy;

    unless($is_stdin) {
        close($input);
    }

    return(%videos);
}

sub fetch_metadata() {
    my (%metadata) = (@_);

    foreach my $listpos (keys %metadata) {
	my @cmd = ("ffprobe",
		   "-v", "panic", "-of", "json", "-show_format",
		   $metadata{$listpos}{'link'});

	open(my $json, "-|", @cmd)
	    or die("Could not open pipe '@cmd' : $!\m");

	my @text = ();
	while (my $j = <$json>) {
	    push (@text, $j);
	}

	close($json);

	my $t = join("", @text);

	my $d = decode_json(join("",@text));

	# print Dumper($d),"\n";

	if ($d->{'format'}->{'tags'}->{'title'}) {
	    $metadata{$listpos}{'title'} = $d->{'format'}->{'tags'}->{'title'};
	}

	if ($d->{'format'}->{'tags'}->{'DATE'}) {
	    $metadata{$listpos}{'date'}  = $d->{'format'}->{'tags'}->{'DATE'};
	}

	# print $metadata{$listpos}{'link'},"\n";
	# print $metadata{$listpos}{'file'},"\n\n";
    }

    return(%metadata);
}

Gemini/gemini-cron-updater.sh

#!/bin/sh

# 2021-02-15	Update TR Gemini site from TR HTTP RSS
#
# fetch the TR RSS feed with the first script
#	 	(gemini-fetch-urls-from-rss.pl)
# fetch the linked web pages with the second,
#		(gemini-fetch-web-page.pl)
#	which internally calls a third script
#		(gemini-parse-html-to-gemini.pl)
# 	that third script converts the page from HTML to Gemini
#	and places the result in the file system hierarchy
# then if successful, rebuild the Gemini indexes using a fourth script
#		(gemini-main-index-template.sh)

PATH=/home/gemini/bin:/usr/local/bin:/usr/bin:/bin

# override environment variables set incorrectly by the SSH client
export LANG="en_GB.UTF-8"
export LC_ALL="en_GB.UTF-8"
export LC_TIME="en_GB.UTF-8"
export LC_MONETARY="en_GB.UTF-8"
export LC_ADDRESS="en_GB.UTF-8"
export LC_TELEPHONE="en_GB.UTF-8"
export LC_NAME="en_GB.UTF-8"
export LC_MEASUREMENT="en_GB.UTF-8"
export LC_IDENTIFICATION="en_GB.UTF-8"
export LC_NUMERIC="en_GB.UTF-8"
export LC_PAPER="en_GB.UTF-8"

lockfile="/tmp/lockfile-$(basename $0)"

if [ "${FLOCK}" != "$lockfile" ]; then
	if flock -en "$lockfile" -c date; then
		exec env FLOCK="$lockfile" flock -en "$lockfile" \
                        -c "$0" "$@"
	else
		echo "Already Locked.  Aborting." >&2
		exit 4
	fi
else
	true
	# echo Locking
fi

sudo /usr/local/sbin/tc-shaper-v2021-Nov.sh

tmpfeeds=$(mktemp --suffix "-cron-gemini") || exit 1
trap "rm -f -- $lockfile; rm -f -- $tmplinks; rm -f -- $tmpindex;" EXIT

set -e

yyyy=$(date +"%Y");

count=5
while ! timeout 200 gemini-fetch-urls-from-rss.pl http://techrights.org/feed/ \
    > ${tmpfeeds}
do
	count=$((${count}-1))
	if [ ${count} -eq 0 ]
	then
		exit 2
	fi
	sleep 5
done

for u in $(grep http ${tmpfeeds});
do
	count=5
	while ! timeout 200 gemini-fetch-web-page.pl \
                -g -p -b /home/gemini/gemini/ ${u}
	do
		count=$((${count}-1))
		if [ ${count} -eq 0 ]
		then
			exit 3
		fi
		sleep 5
	done
done

timeout 600 gemini-inventory.pl -A /home/gemini/gemini/archive/ \
	/home/gemini/gemini/ \
    && gemini-main-index-template.sh > /home/gemini/gemini/index.gmi

# gemini-inventory.pl /home/gemini/gemini/2021 works, too, but
# truncates the archive, if used

gemini-bulletin-irc-update.sh

# let's wait for the site to update its index (as it does
# every 5 minutes) and then retrieve the list in case
# a new video was posted (note that the file of the video
# gets uploaded well before a post gets published, so sleep()
# might be spurious)

sleep 25
gemini-latest-videos.sh

rm -f -- ${lockfile}
rm -f -- ${tmpfeeds}
trap - exit

sudo /usr/local/sbin/tc-shaper-v2.sh

exit 0

Gemini/gemini-index-years-only.sh

#!/bin/sh

# 2021-09013

PATH=/usr/bin:/bin

cd /home/gemini/gemini

find 2??? \
	-maxdepth 0 \
	-type d \
	-exec sh -c "(echo '# Techrights {}\n'; ls -d {}/?? | \
		sed -re 's|^.*/||; s|(.*)|=> \1/ \1|') \
	> {}/index.gmi" \;

exit 0

Gemini/gemini-capcom.gmi

=> gemini://sbg.one/gemfeed/atom.xml
=> gemini://lab6.com/atom.xml
=> gemini://basnja.ru/atom.xml
=> gemini://makeworld.gq/gemlog/atom.xml
=> gemini://si3t.ch/log/atom.xml
=> gemini://alexschroeder.ch:1965/do/blog/atom
=> gemini://capsule.theparanoidtimes.org/posts/atom.xml
=> gemini://ainent.xyz/gemlog/index.gmi
=> gemini://l-ipa.net/atom.xml
=> gemini://gemini.susa.net/atom.xml
=> gemini://tilde.team/~sumpygump/gemlog/atom.xml
=> gemini://heathens.club/~palm93/the_woods/
=> gemini://hoseki.iensu.me/atom.xml
=> gemini://six10.pw
=> gemini://9til.de/users/~julienxx/atom.xml
=> gemini://thfr.info/index.gmi
=> gemini://pachapunk.space/atom.xml
=> gemini://etam-software.eu/
=> gemini://freeshell.de/gemlog/atom.xml
=> gemini://envs.net/~vee/pikkulog/atom.xml
=> gemini://bobignou.red/atom.xml
=> gemini://acidic.website/musings/atom.xml
=> gemini://edwardtefft.com/atom.xml
=> gemini://mizik.eu/feed.xml
=> gemini://miso.town/atom.xml
=> gemini://jdj.golf/gemlog/atom.xml
=> gemini://cap.swan.quest/p/atom.xml
=> gemini://amami.city/~xdefrag/atom.xml
=> gemini://knijn.srht.site/posts/atom.xml
=> gemini://gemlog.blue/users/cariboudatascience/
=> gemini://aprates.dev/log/atom.xml
=> gemini://misterbanal.net/atom.xml
=> gemini://skyjake.fi/gemlog/atom.xml
=> gemini://idf.looting.uk/capslog/atom.xml
=> gemini://dira.cc/gemlog/atom.xml
=> gemini://ebc.li/atom.xml
=> gemini://tanelorn.city/~bouncepaw/gemlog/feed.rss
=> gemini://benj.smol.pub/atom.xml
=> gemini://smcttl.flounder.online/gemlog/atom.xml
=> gemini://gemini.circumlunar.space/users/solderpunk/pikkulog/atom.xml
=> gemini://reisub.nsupdate.info/test/atom.xml
=> gemini://rosenzweig.io/gemlog/atom.xml
=> gemini://xhrpb.com/feed.xml
=> gemini://drewdevault.com/feed.xml
=> gemini://envs.net/~vee/gemlog/atom.xml
=> gemini://sylvaindurand.org/rss.xml
=> gemini://tanso.net/atom.xml
=> gemini://random-projects.net/feed.atom
=> gemini://www.underworld.fr/atom.xml
=> gemini://sylvaindurand.org/feed.xml
=> gemini://cee64.us/feed.gmi
=> gemini://tracciabi.li/whiterabbit/index.gmi
=> gemini://gluonspace.com/gemlog/atom.xml
=> gemini://rtr.kalayaan.xyz/blog.gmi/atom.xml
=> gemini://roughcustomers.com
=> gemini://makeworld.space/gemlog/atom.xml
=> gemini://taoetc.org/
=> gemini://rootkey.co.uk/posts/atom.xml
=> gemini://gemini.ctrl-c.club/~nehrman/gemlog/atom.xml
=> gemini://shit.cx/atom.xml
=> gemini://deblan.io/feed.xml
=> gemini://c3d2.de/news-atom.xml
=> gemini://celehner.com/feed.xml
=> gemini://ondollo.com/~/zert:7C:8E:F5/atom.gem
=> gemini://gemini.circumlunar.space/users/acdw/atom.xml
=> gemini://laika.lk/atom.xml
=> gemini://725.be/index.gmi
=> gemini://www.demorrow.net/atom.xml
=> gemini://republic.circumlunar.space/users/crdpa/blog/atom.xml
=> gemini://muscar.eu/feeds/feed.atom.xml
=> gemini://gemini.circumlunar.space/~adiabatic/atom.xml
=> gemini://beyondneolithic.life/atom.xml
=> gemini://phreedom.club/~tolstoevsky/glog/atom.xml
=> gemini://gemini.mat.services/atom.xml
=> gemini://gemini.bbbhltz.space/gemlog/atom.xml
=> gemini://tilde.club/~lewiscowper/gemlog/atom.xml
=> gemini://posixcafe.org/blogs/feed.atom
=> gemini://shuhao.srht.site/posts/atom.xml
=> gemini://gnuser.land/gemlog/atom.xml
=> gemini://gemlog.blue/users/NetCandide/
=> gemini://spool-five.com/gemlog/index.gmi
=> gemini://c3po.aljadra.xyz/blog.gmi
=> gemini://talon.computer/log/atom.xml
=> gemini://nader.pm/atom.xml
=> gemini://c3po.aljadra.xyz/atom.xml
=> gemini://gemini.ucant.org/gemlog/atom.xml
=> gemini://sylvaindurand.org/atom.xml
=> gemini://low-key.me/gemlog/atom.xml
=> gemini://gem.johanbove.info/gemlog/atom.xml
=> gemini://tilde.pink/~emily/atom.xml
=> gemini://taoetc.org/feed.gmi
=> gemini://gemini.circumlunar.space/users/alchemist/gemlog/atom.xml
=> gemini://warmedal.se/~bjorn/atom.xml
=> gemini://ruario.flounder.online/gemlog/atom.xml
=> gemini://blog.schmidhuberj.de/atom.xml
=> gemini://tilde.team/~ivanruvalcaba/glog/atom.xml
=> gemini://tilde.team/~easeout/glog/atom.xml
=> gemini://koyu.space/blu256/atom.xml
=> gemini://gem.vectorpoem.com/projects_log.gmi
=> gemini://perso.pw/blog/rss.xml

Gemini/watch-for-heavy-users.sh

#!/bin/bash

HEIGHT=15
WIDTH=40
CHOICE_HEIGHT=4
BACKTITLE="DDOS observer"
TITLE="Gemini @ Techrights"
MENU="Choose refresh/recheck interval:"

OPTIONS=(1 "Every 1 minute"
         2 "Every 10 minutes (recommended, sound/beep suppressed)"
         3 "Every 30 minutes"
)

CHOICE=$(dialog --clear \
                --backtitle "$BACKTITLE" \
                --title "$TITLE" \
                --menu "$MENU" \
                $HEIGHT $WIDTH $CHOICE_HEIGHT \
                "${OPTIONS[@]}" \
                2>&1 >/dev/tty)

clear
case $CHOICE in
        1)
            echo "You chose Option 1"
watch -b -c -n 60 ~gemini/bin/show-new-visitors-count.sh ;;
        2)
            echo "You chose Option 2"
watch  -c -n 600 ~gemini/bin/show-new-visitors-count.sh ;;
        3)
            echo "You chose Option 3"
watch -b -c -n 1800 ~gemini/bin/show-new-visitors-count.sh ;;
esac

# watch -b -c -n 600 ~gemini/bin/show-new-visitors-count.sh

Gemini/gemini-feeds.gmi

=> gemini://mozz.us/journal/atom.xml
=> gemini://midnight.pub/feed.xml
=> gemini://gemini.circumlunar.space/users/solderpunk/gemlog/atom.xml
=> gemini://gemini.circumlunar.space/~solderpunk/hitenheroes/posts/atom.xml
=> gemini://gemini.circumlunar.space/~solderpunk/gemlog/atom.xml
=> gemini://gemini.circumlunar.space/~solderpunk/cornedbeef/atom.xml
=> gemini://republic.circumlunar.space/users/flexibeast/gemlog/atom.xml
=> gemini://gemini.circumlunar.space/~ew/feed.xml
=> gemini://drewdevault.com/feed.xml
=> gemini://treeprophet.flounder.online/gemlog/atom.xml
=> gemini://mothbaby.flounder.online/gemlog/atom.xml
=> gemini://cetacean.club/journal/atom.xml
=> gemini://hannuhartikainen.fi/twinlog/atom.xml
=> gemini://gemini.jayeless.net/gemlog/atom.xml
=> gemini://idiomdrottning.org/atom.xml
=> gemini://going-flying.com/~mernisse/atom.xml
=> gemini://breadpunk.club/~bagel/atom.xml
=> gemini://nytpu.com/flightlog/atom.xml
=> gemini://nytpu.com/gemlog/atom.xml
=> gemini://avalos.me/gemlog/atom.xml
=> gemini://rawtext.club/~mieum/prose/atom.xml
=> gemini://breadpunk.club/~snickerdoodles/recipes/atom.xml
=> gemini://rawtext.club/~mieum/relog/atom.xml
=> gemini://rawtext.club/~mieum/poems/atom.xml
=> gemini://gemini.sensorstation.co/atom.xml
=> gemini://envs.net/~kyrenaios/atom.xml
=> gemini://tilde.team/~easeout/glog/atom.xml
=> gemini://gemini.conman.org/boston.atom
=> gemini://carcosa.net/send-the-nukes/atom.xml
=> gemini://carcosa.net/journal/atom.xml
=> gemini://yam655.com/recent-feed.xml
=> gemini://envs.net/~negatethis/atom.xml
=> gemini://upyum.com/journal/feed.atom
=> gemini://1436.ninja/gemlog/gemlog.atom
=> gemini://80h.dev/glog/atom.xml
=> gemini://80h.dev/~int/glog/atom.xml
=> gemini://9til.de/users/~julienxx/atom.xml
=> gemini://caracolito.mooo.com/~sejo/atom.xml
=> gemini://tilde.team/~emilis/atom.xml
=> gemini://republic.circumlunar.space/users/joneworlds/atom.xml
=> gemini://rosenzweig.io/gemlog/atom.xml
=> gemini://breadpunk.club/~bakersdozen/gemlog/atom.xml
=> gemini://calcuode.com/gemlog/atom.xml
=> gemini://salejandro.me/gemlog/atom.xml
=> gemini://gemini.circumlunar.space/users/solderpunk/pikkulog/atom.xml
=> gemini://warmedal.se/~bjorn/atom.xml
=> gemini://gemini.circumlunar.space/~solderpunk/pikkulog/atom.xml
=> gemini://hedy.flounder.online/gemlog/atom.xml
=> gemini://antiphasis.flounder.online/gemlog/atom.xml
=> gemini://asp.flounder.online/gemlog/atom.xml
=> gemini://jfh.me/atom.xml
=> gemini://ecs.d2evs.net/feed.xml
=> gemini://chartjunk.flounder.online/gemlog/atom.xml
=> gemini://enteka.flounder.online/gemlog/atom.xml
=> gemini://gem.acdw.net/do/atom
=> gemini://gem.acdw.net/do/rss
=> gemini://gem.acdw.net/do/all/atom
=> gemini://going-flying.com/thoughts/atom.xml
=> gemini://tilde.team/~ivanruvalcaba/glog/atom.xml
=> gemini://gemini.ctrl-c.club/~nehrman/gemlog/atom.xml
=> gemini://gemini.circumlunar.space/users/adiabatic/atom.xml
=> gemini://rawtext.club/~tolstoevsky/glog/atom.xml
=> gemini://rawtext.club/~verkaro/glog//atom.xml
=> gemini://tilde.club/~lewiscowper/gemlog/atom.xml
=> gemini://gemini.circumlunar.space/users/parker/archives/atom.xml
=> gemini://gemini.circumlunar.space/users/parker/gacme/atom.xml
=> gemini://gemini.circumlunar.space/~swiftmandolin/gemlog/atom.xml
=> gemini://tilde.team/~sumpygump/gemlog/atom.xml
=> gemini://l-3.space/atom.xml
=> gemini://republic.circumlunar.space/users/itsdave/atom.xml
=> gemini://republic.circumlunar.space/~itsdave/noc/atom.xml
=> gemini://republic.circumlunar.space/~itsdave/atom.xml
=> gemini://republic.circumlunar.space/users/luminar/atom_feed.xml
=> gemini://rwv.io/src/atom.xml
=> gemini://rwv.io/atom.xml
=> gemini://m040601.flounder.online/gemlog/atom.xml
=> gemini://testuser.flounder.online/atom.xml
=> gemini://testuser.flounder.online/myfeed.xml
=> gemini://thelovebug.flounder.online/gemlog/atom.xml
=> gemini://zettelkastensystem.flounder.online/gemlog/atom.xml
=> gemini://gem.acdw.net/code/do/atom
=> gemini://gem.acdw.net/code/do/rss
=> gemini://gem.acdw.net/food/do/atom
=> gemini://gem.acdw.net/food/do/rss
=> gemini://gem.acdw.net/life/do/atom
=> gemini://gem.acdw.net/life/do/rss
=> gemini://gem.acdw.net/made/do/atom
=> gemini://gem.acdw.net/made/do/rss
=> gemini://gem.acdw.net/pub/do/atom
=> gemini://gem.acdw.net/pub/do/rss
=> gemini://gem.acdw.net/text/do/atom
=> gemini://gem.acdw.net/text/do/rss
=> gemini://gemini.susa.net/atom.xml
=> gemini://pulham.info/atom.xml
=> gemini://mrnd.xyz/log/atom.xml
=> gemini://her.esy.fun/gem-atom.xml
=> gemini://celehner.com/feed.xml
=> gemini://gemini.circumlunar.space/~solderpunk/3albums/atom.xml
=> gemini://otrn.org/atom.xml
=> gemini://acidic.website/musings/atom.xml
=> gemini://gemini.thegonz.net/glog/atom.xml
=> gemini://samsai.eu/gemlog/atom.xml
=> gemini://apintandaparma.club/~ajc/log/atom.xml
=> gemini://sunshinegardens.org/~xj9/feed.atom
=> gemini://envs.net/~lrb/gemlog/atom.xml
=> gemini://perplexing.space/atom.xml
=> gemini://gemini.circumlunar.space/~adiabatic/atom.xml
=> gemini://apintandaparma.club/~ajft/phlog/atom.xml
=> gemini://gempaper.strangled.net/notes/atom.xml
=> gemini://republic.circumlunar.space/users/dbane/gemlog/atom.xml
=> gemini://tilde.pink/~lucymoth/gemlog/atom.xml
=> gemini://aoalmeida.com/feed.xml
=> gemini://shit.cx/atom.xml
=> gemini://tilde.team/~emilis/feed.xml
=> gemini://gsthnz.com/posts/atom.xml
=> gemini://skyjake.fi/gemlog/atom.xml
=> gemini://emii.gay/en/emi-overshares/atom.xml
=> gemini://republic.circumlunar.space/~luminar/atom_feed.xml
=> gemini://drsudo.com/gemlog/atom.xml
=> gemini://gemini.ucant.org/gemlog/atom.xml
=> gemini://gemini.iosa.it/gemlog/atom.xml
=> gemini://freeside.wntrmute.net/log/index.rss
=> gemini://orx57.flounder.online/gemlog/atom.xml
=> gemini://rawtext.club/~mieum/music/atom.xml
=> gemini://rawtext.club/~ecliptik/_posts/feed.xml
=> gemini://mnmnm.flounder.online/gemlog/atom.xml
=> gemini://kujiu.org/blog/atom.xml
=> gemini://nerv-project.eu/blog/atom.xml
=> gemini://gmi.karl.berlin/atom.xml
=> gemini://tposs.flounder.online/gemlog/atom.xml
=> gemini://gemini.lottalinuxlinks.com/posts/feed.xml
=> gemini://nader.pm/atom.xml
=> gemini://dsfadsfgafgf.flounder.online/gemlog/atom.xml
=> gemini://space.fqserv.eu/gemlog/atom.xml
=> gemini://moddedbear.xyz/logs/atom.xml
=> gemini://lightspeed.flounder.online/gemlog/atom.xml
=> gemini://shtirlic.flounder.online/gemlog/atom.xml
=> gemini://makeworld.space/users/~atyrfingerprints/gemlog/atom.xml
=> gemini://makeworld.space/gemlog/atom.xml
=> gemini://phreedom.club//atom.xml
=> gemini://jochen.flounder.online/gemlog/atom.xml
=> gemini://lycopersica.flounder.online/gemlog/atom.xml
=> gemini://rasputin.selfip.net/atom.xml
=> gemini://space.fqserv.eu/tanka/atom.xml
=> gemini://thmslld.flounder.online/gemlog/atom.xml
=> gemini://moribundo.flounder.online/gemlog/atom.xml
=> gemini://gemini.cyberbot.space/gemlog/atom.xml
=> gemini://breadpunk.club/~proof/atom.xml
=> gemini://breadpunk.club/~snickerdoodles/mouthfuls/atom.xml
=> gemini://riqtare.flounder.online/gemlog/atom.xml
=> gemini://przemek.flounder.online/gemlog/atom.xml
=> gemini://szczezuja.flounder.online/gemlog/atom.xml
=> gemini://votih.flounder.online/gemlog/atom.xml
=> gemini://cosmic.voyage/atom.xml
=> gemini://random-projects.net/feed.atom
=> gemini://muppet.flounder.online/gemlog/atom.xml
=> gemini://scifirenegade.flounder.online/gemlog/atom.xml
=> gemini://etam-software.eu/blog/feed.xml
=> gemini://adnano.co/atom.xml
=> gemini://edwardtefft.com/atom.xml
=> gemini://t-900.flounder.online/gemlog/atom.xml
=> gemini://sunbance.flounder.online/gemlog/atom.xml
=> gemini://jlk.flounder.online/gemlog/atom.xml
=> gemini://capsule.usebox.net/gemlog/atom.xml
=> gemini://filter.id.au/gemlog/atom.xml
=> gemini://tilde.pink/~emily/atom.xml
=> gemini://senders.io/feed/atom.xml
=> gemini://senders.io/gemlog/feed/atom.xml
=> gemini://1436.ninja/phlog.atom
=> gemini://flume.space/atom.xml
=> gemini://wirthslaw.flounder.online/gemlog/atom.xml
=> gemini://pwshnotes.flounder.online/gemlog/atom.xml
=> gemini://bonehead.flounder.online/gemlog/atom.xml
=> gemini://luke.flounder.online/gemlog/atom.xml
=> gemini://gemini.grappling.ca/gemlog/atom.xml
=> gemini://fungihirn.flounder.online/gemlog/atom.xml
=> gemini://mederle.de/feed-de
=> gemini://mederle.de/feed-en
=> gemini://heathens.club/~palm93/atom.xml
=> gemini://tilde.team/~ivanruvalcaba/blog/atom.xml
=> gemini://tilde.club/~liamvhogan/blog/atom.xml
=> gemini://soviet.circumlunar.space/markov/schismatrix-notes/atom.xml
=> gemini://phreedom.club/~tolstoevsky/glog/atom.xml
=> gemini://tilde.pink/~monerulo/atom.xml
=> gemini://posixcafe.org/blogs/feed.atom
=> gemini://posixcafe.org/vocaloid/feed.atom
=> gemini://gemini.mcgillij.dev/atom.xml
=> gemini://gemini.bunburya.eu/gemlog/posts/atom.xml
=> gemini://ew.srht.site/feed.xml
=> gemini://dantes.flounder.online/gemlog/atom.xml
=> gemini://my32.flounder.online/gemlog/atom.xml
=> gemini://si3t.ch/log/atom.xml
=> gemini://gemini.circumlunar.space/~alchemist/gemlog/atom.xml
=> gemini://blog.snowfrost.garden/atom.xml
=> gemini://www.underworld.fr/blog/atom.xml
=> gemini://envs.net/~vee/gemlog/atom.xml
=> gemini://gem.rmgr.dev/blog/atom.xml
=> gemini://inconsistentuniverse.space/atom.xml
=> gemini://s.tymo.name/log/atom.xml
=> gemini://envs.net/~vee/pikkulog/atom.xml
=> gemini://gem.pwarren.id.au/gemlog/atom.xml
=> gemini://rawtext.club/~mieum/dallok/atom.xml
=> gemini://qd.discordian.de/entries/atom.xml
=> gemini://tilde.pink/~hektor/atom.xml
=> gemini://caolan.uk/atom.xml
=> gemini://gemini.astropirados.space/gemlog/atom.xml
=> gemini://www.underworld.fr/atom.xml
=> gemini://g.dumke.me/log/atom.xml
=> gemini://rek2.hispagatos.org/posts/atom.xml
=> gemini://gemini.ctrl-c.club/~axeflayer/atom.xml
=> gemini://bi-rabittoh.flounder.online/gemlog/atom.xml
=> gemini://axiomatika.flounder.online/gemlog/atom.xml
=> gemini://cdaniels.net/feed_http.rss
=> gemini://cdaniels.net/feed_http.atom
=> gemini://cdaniels.net/feed_gmi.rss
=> gemini://cdaniels.net/feed_gmi.atom
=> gemini://buetow.org/gemfeed/atom.xml
=> gemini://gemini.cyberbot.space/smolzine/atom.xml
=> gemini://0gitnick.flounder.online/atom.xml
=> gemini://nicksphere.com/atom.xml
=> gemini://crystal.flounder.online/gemlog/atom.xml
=> gemini://ilogique.flounder.online/gemlog/atom.xml
=> gemini://renedarioherrera.flounder.online/gemlog/atom.xml
=> gemini://thek3nger.flounder.online/gemlog/atom.xml
=> gemini://breadpunk.club/~snickerdoodles/meta/atom.xml
=> gemini://figbert.com/log/atom.xml
=> gemini://szczezuja.space/gemlog/atom.xml
=> gemini://birabittoh.smol.pub/atom.xml
=> gemini://ocramoi.flounder.online/gemlog/atom.xml
=> gemini://corstar.flounder.online/gemlog/atom.xml
=> gemini://freeshell.de/gemlog/atom.xml
=> gemini://degrowther.smol.pub/atom.xml
=> gemini://starbreaker.smol.pub/atom.xml
=> gemini://edim.flounder.online/gemlog/atom.xml
=> gemini://gnuser.land/gemlog/atom.xml
=> gemini://thwidge.flounder.online/gemlog/atom.xml
=> gemini://ghostglyph.flounder.online/gemlog/atom.xml
=> gemini://tilde.cafe/~hedy/feed.xml
=> gemini://gemini.marmaladefoo.com/cgi-bin/atom-feed.cgi?lukee
=> gemini://mieum.smol.pub/atom.xml
=> gemini://hedy.tilde.cafe/feed.xml
=> gemini://gemini.spam.works/users/emery/atom.feed
=> gemini://memex.smol.pub/atom.xml
=> gemini://compudanzas.net/atom.xml
=> gemini://euromancer.flounder.online/gemlog/atom.xml
=> gemini://0x80.org/gemlog/atom.xml
=> gemini://hedy.smol.pub/atom.xml
=> gemini://reisub.nsupdate.info/fabianbonetti/atom.xml
=> gemini://cobaltblue.flounder.online/gemlog/atom.xml
=> gemini://erifnella.flounder.online/gemlog/atom.xml
=> gemini://tilde.team/~supernova/gemlog/atom.xml
=> gemini://rawtext.club/~mieum/atom.xml
=> gemini://blog.locrian.zone/atom.xml
=> gemini://snonux.de/gemfeed/atom.xml
=> gemini://hugeping.ru/atom.xml
=> gemini://melyanna.flounder.online/gemlog/atom.xml
=> gemini://miguelmurca.flounder.online/gemlog/atom.xml
=> gemini://november.smol.pub/atom.xml
=> gemini://cowface.online/gemlog/atom.xml
=> gemini://fawn.garden/log/atom.xml
=> gemini://tilde.team/~g1n/blog/feed.rss
=> gemini://subphase.xyz/users/flow/gemlog/atom.xml
=> gemini://twh.flounder.online/gemlog/atom.xml
=> gemini://jayeless.flounder.online/gemlog/atom.xml
=> gemini://devinprater.flounder.online/gemlog/atom.xml
=> gemini://alsd.eu/it/feed.xml
=> gemini://alsd.eu/en/feed.xml
=> gemini://bleyble.com/users/quokka/atom.xml
=> gemini://dimension.sh/~nikdoof/logs/atom.xml
=> gemini://tilde.team/~blumenkiste/atom.xml
=> gemini://thelambdalab.xyz/atom.xml
=> gemini://matthewhall.xyz/gemlog/atom.xml
=> gemini://karmanyaah.malhotra.cc/gemlog/atom.xml
=> gemini://gmi.bacardi55.io/gemlog/atom.xml
=> gemini://gem.keazilla.net/atom.xml
=> gemini://gemini.alexandrevicente.net/atom.xml
=> gemini://dira.cc/gemlog/atom.xml
=> gemini://aperalesf.flounder.online/gemlog/atom.xml
=> gemini://nickj.flounder.online/gemlog/atom.xml
=> gemini://reisub.nsupdate.info/planetalibre/atom.xml
=> gemini://dustypenguin.flounder.online/gemlog/atom.xml
=> gemini://g.mikf.pl/gemlog/atom.xml
=> gemini://mkf.flounder.online/gemlog/atom.xml
=> gemini://monika.flounder.online/gemlog/atom.xml
=> gemini://kota.nz/atom.xml
=> gemini://mischk.flounder.online/gemlog/atom.xml
=> gemini://epi.benthic.zone/atom.xml
=> gemini://byzoni.org/gemlog/atom.xml
=> gemini://orkney.flounder.online/gemlog/atom.xml
=> gemini://isoraqathedh.pollux.casa/atom.xml
=> gemini://hugeping.ru/micro/atom.xml
=> gemini://danihopera.flounder.online/gemlog/atom.xml
=> gemini://niedzwiedzinski.cyou/feed.xml
=> gemini://hugeping.tk/micro/atom.xml
=> gemini://miso.town/atom.xml
=> gemini://m15o.smol.pub/atom.xml
=> gemini://ichthys.tilde.cafe/atom.xml
=> gemini://marginalia.nu/log/feed.xml
=> gemini://envs.net/~anonyth/atom.xml
=> gemini://sev.flounder.online/gemlog/atom.xml
=> gemini://ataraxia.flounder.online/gemlog/atom.xml
=> gemini://privacy.flounder.online/gemlog/atom.xml
=> gemini://gemini.panda-roux.dev/log/feed.xml
=> gemini://foobucket.xyz/gemlog/atom.xml
=> gemini://warmedal.se/~antenna/atom.xml
=> gemini://maren.flounder.online/gemlog/atom.xml
=> gemini://eaplmx.smol.pub/atom.xml
=> gemini://envs.net/~armen/gemlog/atom.xml
=> gemini://mieum.smol.space/atom.xml
=> gemini://phreedom.club/atom.xml
=> gemini://blog.datapulp.de/atom.xml
=> gemini://datapulp.smol.pub/atom.xml
=> gemini://aprates.dev/log/atom.xml
=> gemini://alexwennerberg.com/gemlog/atom.xml
=> gemini://alex.flounder.online/gemlog/atom.xml
=> gemini://text.eapl.mx/atom.xml
=> gemini://jsreed5.org/feeds/log.xml
=> gemini://jsreed5.org/feeds/math.xml
=> gemini://jsreed5.org/feeds/changelog.xml
=> gemini://soviet.circumlunar.space/wholesomedonut/gemlog/atom.xml
=> gemini://drafts.cyclexo.com/gemlog/atom.xml
=> gemini://s0.is/projects/atom.xml
=> gemini://tilde.cafe/~ichthys/atom.xml
=> gemini://iceworks.cc/z/atom.xml
=> gemini://vy.binarylab.eu/gemlog/atom.xml
=> gemini://officialdonut.smol.pub/atom.xml
=> gemini://viridian.flounder.online/gemlog/atom.xml
=> gemini://hajime.4teri.de/dotfiles/atom.xml
=> gemini://gemini.ctrl-c.club/~nristen/gemlog/atom.xml
=> gemini://aprates.dev/pt-br/log/atom.xml
=> gemini://stacksmith.flounder.online/gemlog/atom.xml
=> gemini://blekksprut.net/nikki/atom.xml
=> gemini://breadpunk.club/~snickerdoodles/atom.xml
=> gemini://josias.dev/gemlog/feed.xml
=> gemini://szczezuja.space/git/scripts/atom.xml
=> gemini://szczezuja.space/git/gmidiff/atom.xml
=> gemini://tilde.team/~tomasino/atom.xml
=> gemini://beyondneolithic.life/atom.xml
=> gemini://lertsenem.com/atom.xml
=> gemini://tilde.town/~nihilazo/atom.xml
=> gemini://moonrockcenter.flounder.online/gemlog/atom.xml
=> gemini://gemini.bvnf.space/blog.rss
=> gemini://ichi.smol.pub/atom.xml
=> gemini://sequel.space/bubble/K1gTCL2PVZJN7ZZ1VPFp98/atom.xml
=> gemini://sequel.space/bubble/ViPx8vf3YSCUisLSKW1rzT/atom.xml
=> gemini://sequel.space/bubble/PVnyeuvYXKkA18UFFC6ZZy/atom.xml
=> gemini://sequel.space/bubble/GgZW37jjdLk4JPSeie7tCW/atom.xml
=> gemini://sequel.space/bubble/CWYmF993GKX94aSwufXU5y/atom.xml
=> gemini://sequel.space/bubble/Xg2Zdu3nYdSDMKD6qBiYM3/atom.xml
=> gemini://sequel.space/bubble/9NmeAJSkwGTHtoS3Xr1rw2/atom.xml
=> gemini://sequel.space/bubble/H6ognLcBjqQSa6MhbuDGKN/atom.xml
=> gemini://sequel.space/bubble/GLTatsSbmzzejurBzJ5dHS/atom.xml
=> gemini://sequel.space/bubble/7h3uWLuZbgzvB99d4rdnPC/atom.xml
=> gemini://sequel.space/bubble/TTToQ7FsbQGqw23SYzQ5un/atom.xml
=> gemini://sequel.space/bubble/HngiQfQ9cAQChi5ghRTcAF/atom.xml
=> gemini://sequel.space/bubble/9octt9FwLbbVumUkpjUgZP/atom.xml
=> gemini://sequel.space/bubble/Xm395CWNAT8fbUAt8dmVQr/atom.xml
=> gemini://sequel.space/bubble/Azs3YBJ78ytjuFgzf5KyHV/atom.xml
=> gemini://sequel.space/bubble/5Z3SGnuJEJND3tsR9iBrgM/atom.xml
=> gemini://sequel.space/bubble/JUQwKFrE8qKWH265BdYp3U/atom.xml
=> gemini://sequel.space/bubble/6EyhnusCdAV5MseoLFU9o1/atom.xml
=> gemini://sequel.space/bubble/JguD5Ws1tDvvfJrbKN7TGW/atom.xml
=> gemini://sequel.space/bubble/NXP4oChaEPJpd5rSkRPsk1/atom.xml
=> gemini://sequel.space/bubble/RnA2gV1Lcj6nFvdHRxRoYp/atom.xml
=> gemini://sbg.one/SandboxGeneral/atom.xml
=> gemini://gemini.circumlunar.space:1965/users/adiabatic/atom.xml
=> gemini://auragem.space/devlog/atom.xml
=> gemini://ax.flounder.online/gemlog/atom.xml
=> gemini://galaxyhub.uk/articles/atom.xml
=> gemini://gemini.circumlunar.space:1965/~solderpunk/gemlog/atom.xml
=> gemini://gem.acdw.net:1965/do/atom
=> gemini://gem.acdw.net:1965/do/rss
=> gemini://gem.acdw.net:1965/do/all/atom
=> gemini://gem.acdw.net:1965/code/do/atom
=> gemini://gem.acdw.net:1965/code/do/rss
=> gemini://gem.acdw.net:1965/food/do/atom
=> gemini://gem.acdw.net:1965/food/do/rss
=> gemini://gem.acdw.net:1965/life/do/atom
=> gemini://gem.acdw.net:1965/life/do/rss
=> gemini://gem.acdw.net:1965/made/do/atom
=> gemini://gem.acdw.net:1965/made/do/rss
=> gemini://gem.acdw.net:1965/pub/do/atom
=> gemini://gem.acdw.net:1965/pub/do/rss
=> gemini://gem.acdw.net:1965/text/do/atom
=> gemini://gem.acdw.net:1965/text/do/rss
=> gemini://gemini.circumlunar.space:1965/users/parker/archives/atom.xml
=> gemini://gemini.circumlunar.space:1965/users/parker/gacme/atom.xml
=> gemini://gemini.circumlunar.space:1965/users/rbasile/blog/feed.atom
=> gemini://gemini.circumlunar.space:1965/users/solderpunk/gemlog/atom.xml
=> gemini://gemini.circumlunar.space:1965/users/solderpunk/pikkulog/atom.xml
=> gemini://mizik.eu/feed.xml
=> gemini://rawtext.club:1965/~mieum/atom.xml
=> gemini://rawtext.club:1965/~mieum/poems/atom.xml
=> gemini://rawtext.club:1965/~mieum/prose/atom.xml
=> gemini://rawtext.club:1965/~mieum/relog/atom.xml
=> gemini://rawtext.club:1965/~mieum/music/atom.xml
=> gemini://gemini.thegonz.net:1965/glog/atom.xml
=> gemini://soviet.circumlunar.space:1965/wholesomedonut/gemlog/atom.xml
=> gemini://mozz.us:1965/journal/atom.xml
=> gemini://hannuhartikainen.fi:1965/twinlog/atom.xml
=> gemini://80h.dev:1965/glog/atom.xml
=> gemini://80h.dev:1965/~int/glog/atom.xml
=> gemini://exul.flounder.online/gemlog/atom.xml
=> gemini://latte.flounder.online/gemlog/atom.xml
=> gemini://pkill9.flounder.online/gemlog/atom.xml
=> gemini://sequel.space/bubble/XxLSRdjrZmctoE3CUmpnd2/atom.xml
=> gemini://sequel.space/bubble/VPxAbFt11WndPpYh1svnVS/atom.xml
=> gemini://sequel.space/bubble/V3pHHE5npaGdZ5cv88qMVq/atom.xml
=> gemini://inthetrees.flounder.online/gemlog/atom.xml
=> gemini://jordir.flounder.online/gemlog/atom.xml
=> gemini://ivanruvalcaba.cf/atom.xml
=> gemini://gemini.cyberbot.space:1965/gemlog/atom.xml
=> gemini://gemini.cyberbot.space:1965/smolzine/atom.xml
=> gemini://freeside.wntrmute.net:1965/log/index.rss
=> gemini://gemini.ucant.org:1965/gemlog/atom.xml
=> gemini://szczezuja.flounder.online/git/scripts/atom.xml
=> gemini://szczezuja.flounder.online/git/gmidiff/atom.xml
=> gemini://sjo.smol.pub/atom.xml
=> gemini://lyk.so/feed.atom
=> gemini://g.moi.lc/atom.xml
=> gemini://unixcat.coffee/gemlog/atom.xml
=> gemini://unixcat.coffee/recipes/atom.xml
=> gemini://armen138.flounder.online/gemlog/atom.xml
=> gemini://nicksphere.ch/atom.xml
=> gemini://frogs.flounder.online/gemlog/atom.xml
=> gemini://johnstephens.flounder.online/gemlog/atom.xml
=> gemini://stuserw.smol.pub/atom.xml
=> gemini://tilde.team/~konomo/atom.xml
=> gemini://blekksprut.net/日常鑑賞/atom.xml
=> gemini://bogart.flounder.online/gemlog/atom.xml
=> gemini://ruario.flounder.online/gemlog/atom.xml
=> gemini://lantashifiles.com/gemlog/entries/atom.xml
=> gemini://sotiris.papatheodorou.xyz/gemlog/atom.xml
=> gemini://vk3.wtf/gemlog/atom.xml
=> gemini://karabas.flounder.online/gemlog/atom.xml
=> gemini://sixohthree.com/atom.xml
=> gemini://bitdweller.flounder.online/gemlog/atom.xml
=> gemini://jdj.golf/gemlog/atom.xml
=> gemini://spxtr.net/atom.xml
=> gemini://yretek.com/atom.xml
=> gemini://gemini.ctrl-c.club/~pipe/atom.xml
=> gemini://low-key.me/gemlog/atom.xml
=> gemini://rasputin.selfip.net:1965/atom.xml
=> gemini://moddedbear.xyz:1965/logs/atom.xml
=> gemini://spool-five.com/gemlog/atom.xml
=> gemini://jfh.me:1965/atom.xml
=> gemini://going-flying.com:1965/thoughts/atom.xml
=> gemini://199.247.10.62/users/~julienxx/atom.xml
=> gemini://amadeus.flounder.online/gemlog/atom.xml
=> gemini://tentree.flounder.online/gemlog/atom.xml
=> gemini://hoseki.iensu.me/atom.xml
=> gemini://freeshell.de:1965/gemlog/atom.xml
=> gemini://koyu.space/blu256/atom.xml
=> gemini://dvd.flounder.online/feed.xml
=> gemini://sysnull.info/blog/atom.xml
=> gemini://www.groovestomp.com/gemlog/atom.xml
=> gemini://www.groovestomp.com/poetry/atom.xml
=> gemini://sequel.space/bubble/UcDBktgT6fVzeg7mhzSMMU/atom.xml
=> gemini://smol.pub/atom.xml
=> gemini://megymagy.flounder.online/gemlog/atom.xml
=> gemini://parsley.smol.pub/atom.xml
=> gemini://pietro.flounder.online/gemlog/atom.xml
=> gemini://sillylaird.flounder.online/gemlog/atom.xml
=> gemini://winter.flounder.online/gemlog/atom.xml
=> gemini://asciibene.flounder.online/gemlog/atom.xml
=> gemini://parsley.farm/atom.xml
=> gemini://cetacean.club:1965/journal/atom.xml
=> gemini://gluonspace.com/gemlog/atom.xml
=> gemini://kujiu.eu/blog/atom.xml
=> gemini://www.snonux.de/gemfeed/atom.xml
=> gemini://www.buetow.org/gemfeed/atom.xml
=> gemini://sequel.space/bubble/7nHibfwwMvZQvdt1LAA4P3/atom.xml
=> gemini://ruario.flounder.online/journal-atom.xml
=> gemini://text.adventuregameclub.com/atom.xml
=> gemini://foo.zone/gemfeed/atom.xml
=> gemini://www.foo.zone/gemfeed/atom.xml
=> gemini://gemini.circumlunar.space/news/atom.xml
=> gemini://gemini.circumlunar.space:1965/news/atom.xml
=> gemini://buetow.org:1965/gemfeed/atom.xml
=> gemini://nytpu.com:1965/flightlog/atom.xml
=> gemini://compudanzas.net:1965/atom.xml
=> gemini://alsd.eu:1965/it/feed.xml
=> gemini://calcuode.com:1965/gemlog/atom.xml
=> gemini://hedy.tilde.cafe:1965/feed.xml
=> gemini://salejandro.me:1965/gemlog/atom.xml
=> gemini://alsd.eu:1965/en/feed.xml
=> gemini://breadpunk.club:1965/~bakersdozen/gemlog/atom.xml
=> gemini://etam-software.eu:1965/blog/feed.xml
=> gemini://blog.schmidhuberj.de/atom.xml
=> gemini://tilde.team:1965/~konomo/atom.xml
=> gemini://tilde.pink:1965/~monerulo/atom.xml
=> gemini://dira.cc:1965/gemlog/atom.xml
=> gemini://corscada.uk/gemlog/atom.xml
=> gemini://bvnf.space/blog.rss
=> gemini://dns.eric.jetzt/gemlog/atom.xml
=> gemini://knijn.ga/posts/atom.xml
=> gemini://dctrud.randomroad.net/gemlog/atom.xml
=> gemini://cipay.ca/atom.xml
=> gemini://sanelkukic.smol.pub/atom.xml
=> gemini://costas.dev/posts/atom.xml
=> gemini://kayvr.com/gemlog/feed.xml
=> gemini://rawtext.club:1965/~verkaro/glog//atom.xml
=> gemini://sequel.space/bubble/8XvescodwwhNVjLVhV2HcP/atom.xml
=> gemini://dctrud.randomroad.net:1965/gemlog/atom.xml
=> gemini://cjc.im/feed.xml
=> gemini://mysidard.com/gemlog/public/atom.xml
=> gemini://colincogle.name/blog/atom.xml
=> gemini://evenstar.flounder.online/gemlog/atom.xml
=> gemini://saurabh.flounder.online/gemlog/atom.xml
=> gemini://tilde.club/~kerobaros/glog//atom.xml
=> gemini://hashtagueule.fr/atom.xml
=> gemini://armitage.flounder.online/gemlog/atom.xml
=> gemini://r2aze.observer/atom.xml
=> gemini://head.baselab.org/gemlog/atom.xml
=> gemini://0gitnick.flounder.online:1965/atom.xml
=> gemini://birabittoh.smol.pub:1965/atom.xml
=> gemini://blekksprut.net:1965/nikki/atom.xml
=> gemini://byzoni.org:1965/gemlog/atom.xml
=> gemini://capsule.usebox.net:1965/gemlog/atom.xml
=> gemini://causa-arcana.com:1965/blog/feed.xml
=> gemini://cdaniels.net:1965/feed_http.rss
=> gemini://cdaniels.net:1965/feed_http.atom
=> gemini://cdaniels.net:1965/feed_gmi.rss
=> gemini://cdaniels.net:1965/feed_gmi.atom
=> gemini://celehner.com:1965/feed.xml
=> gemini://cipay.ca:1965/atom.xml
=> gemini://dvd.flounder.online:1965/feed.xml
=> gemini://edwardtefft.com:1965/atom.xml
=> gemini://epi.benthic.zone:1965/atom.xml
=> gemini://g.dumke.me:1965/log/atom.xml
=> gemini://gluonspace.com:1965/gemlog/atom.xml
=> gemini://gmi.karl.berlin:1965/atom.xml
=> gemini://gsthnz.com:1965/posts/atom.xml
=> gemini://isoraqathedh.pollux.casa:1965/atom.xml
=> gemini://josias.dev:1965/gemlog/feed.xml
=> gemini://karmanyaah.malhotra.cc:1965/gemlog/atom.xml
=> gemini://makeworld.space:1965/gemlog/atom.xml
=> gemini://mysidard.com:1965/gemlog/public/atom.xml
=> gemini://nicksphere.com:1965/atom.xml
=> gemini://november.smol.pub:1965/atom.xml
=> gemini://nytpu.com:1965/gemlog/atom.xml
=> gemini://olivescout.flounder.online/gemlog/atom.xml
=> gemini://perplexing.space:1965/atom.xml
=> gemini://pulham.info:1965/atom.xml
=> gemini://rfmpie.smol.pub/atom.xml
=> gemini://ruario.flounder.online:1965/journal-atom.xml
=> gemini://rwv.io:1965/src/atom.xml
=> gemini://sixohthree.com:1965/atom.xml
=> gemini://testuser.flounder.online:1965/atom.xml
=> gemini://testuser.flounder.online:1965/myfeed.xml
=> gemini://thelambdalab.xyz:1965/atom.xml
=> gemini://unbinding.flounder.online/gemlog/atom.xml
=> gemini://vk3.wtf:1965/gemlog/atom.xml
=> gemini://yvngwytch840.flounder.online/gemlog/atom.xml
=> gemini://crash.smol.pub/atom.xml
=> gemini://longform.flounder.online/gemlog/atom.xml
=> gemini://binarycat.flounder.online/gemlog/atom.xml
=> gemini://malinfreeborn.com/gen/atom.xml
=> gemini://nafmusings.xyz/atom.xml
=> gemini://causa-arcana.com/blog/feed.xml
=> gemini://gemini.conman.org:1965/boston.atom
=> gemini://vectorprime.deszaras.xyz:1965/passages/atom.xml
=> gemini://kirill.zholnay.name/gemfeed/atom.xml
=> gemini://ttrpgs.org/all/atom.xml
=> gemini://seydaneen.nahtgards.de/leuchtturm/dwemerartefakte/gemini-circumlunar-space/news/atom.xml
=> gemini://gemini.locrian.zone/gemini/news/atom.xml
=> gemini://gemini.locrian.zone:1965/gemini/news/atom.xml
=> gemini://gemini.circumlunar.space/users/rbasile/blog/feed.atom
=> gemini://cap.swan.quest/p/atom.xml
=> gemini://rawtext.club:1965/~mieum/dallok/atom.xml

# CAPCOM:

=> gemini://voidcruiser.nl
=> gemini://station.martinrue.com/martin/4c5f8b1291a049319033ad1c33aa373d
=> gemini://g.xn--nck.club/feed.xml
=> gemini://mozz.us/journal/atom.xml
=> gemini://gemlog.blue/users/futagoza/1619481193.gmi
=> gemini://monmac.flounder.online
=> gemini://gem.acdw.net/do/rss
=> gemini://otrn.org/atom.xml
=> gemini://mrnd.xyz/log/atom.xml
=> gemini://freeshell.de/gemlog/atom.xml
=> gemini://nuclear.discrust.pl
=> gemini://posixcafe.org/blogs/feed.atom
=> gemini://republic.circumlunar.space/users/dbane/gemlog/atom.xml
=> gemini://overeducated-redneck.net/glog/atom.xml
=> gemini://nalie.art
=> gemini://midnight.pub/feed.xml
=> gemini://decelerationist.net/feed.xml
=> gemini://gemini.bvnf.space/blog.rss
=> gemini://t-900.flounder.online/gemlog/atom.xml
=> gemini://calcuode.com/gemlog/atom.xml
=> gemini://calmwaters.xyz
=> gemini://six10.pw
=> gemini://sotiris.papatheodorou.xyz/gemlog/atom.xml
=> gemini://gemini.susa.net/atom.xml
=> gemini://gem.informethique.org/recettes/atom.xml
=> gemini://om.gay/redacted/gmi.atom
=> gemini://foo.zone/gemfeed/atom.xml
=> gemini://thesudorm.com/atom.xml
=> gemini://gem.rmgr.dev/blog/atom.xml
=> gemini://luke.flounder.online/gemlog/atom.xml
=> gemini://gemini.ctrl-c.club/~stack/gemlog/
=> gemini://gemini.kevinronceray.com/atom.xml
=> gemini://hannuhartikainen.fi/twinlog/atom.xml
=> gemini://fortunebot.crabdance.com/feed/atom.xml
=> gemini://rawtext.club/~tolstoevsky/glog/atom.xml
=> gemini://tilde.team/~remyabel/atom.xml
=> gemini://ybad.name/log/feed.atom
=> gemini://soviet.circumlunar.space/~hektor/atom.xml
=> gemini://xj-ix.luxe:1969/feed.atom
=> gemini://pietro.flounder.online/gemlog
=> gemini://soviet.circumlunar.space/wholesomedonut/gemlog/atom.xml
=> gemini://jlk.flounder.online/gemlog/
=> gemini://l-3.space/atom.xml
=> gemini://cdaniels.net/feed_gmi.atom
=> gemini://republic.circumlunar.space/users/crdpa/blog/atom.xml
=> gemini://bcn08012.ddns.net/atom.xml
=> gemini://blog.hispagatos.org
=> gemini://gemini.marmaladefoo.com/cgi-bin/atom-feed.cgi?lukee
=> gemini://simbly.me/posts/atom.xml
=> gemini://idf.looting.uk/capslog/atom.xml
=> gemini://oberdada.pollux.casa/gemlog.gmi
=> gemini://tilde.club/~lewiscowper/gemlog/atom.xml
=> gemini://gemlog.blue/users/cariboudatascience/1613780342.gmi
=> gemini://avalos.me/gemlog/atom.xml
=> gemini://inconsistentuniverse.space/atom.xml
=> gemini://carcosa.net/journal/atom.xml
=> gemini://tilde.team/~steve/atom.xml
=> gemini://www.demorrow.net/atom.xml
=> gemini://republic.circumlunar.space/crdpa/blog/atom.xml
=> gemini://skyjake.fi/gemlog/atom.xml
=> gemini://wirthslaw.flounder.online/gemlog/atom.xml
=> gemini://gemini.maxxk.me/atom.xml
=> gemini://sdf.org/atom.xml
=> gemini://rawtext.club/~ploum/atom.xml
=> gemini://benj.smol.pub/atom.xml
=> gemini://l-ipa.net/atom.xml
=> gemini://gemini.go350.com/atom.xml
=> gemini://subphase.xyz/users/flow/gemlog/atom.xml
=> gemini://gemlog.blue/users/cariboudatascience/
=> gemini://mikelynch.org/index.gmi
=> gemini://gemini.strahinja.org/blog/rss.xml
=> gemini://quasivoid.net/gemlog/atom.xml
=> gemini://alsd.eu/en/feed.xml
=> gemini://hugeping.tk/atom.xml
=> gemini://ondollo.com/~/zert:7C:8E:F5/atom.gem
=> gemini://g.dumke.me/log/atom.xml
=> gemini://tilde.team/~easeout/glog/atom.xml
=> gemini://gemini.bbbhltz.space/gemlog/atom.xml
=> gemini://tilde.team/~ivanruvalcaba/glog/atom.xml
=> gemini://alexschroeder.ch:1965/do/blog/atom
=> gemini://starbreaker.org/atom.xml
=> gemini://blog.snowfrost.garden/atom.xml
=> gemini://blog.schmidhuberj.de/atom.xml
=> gemini://cadadr.space/blag.gmi
=> gemini://gemini.ctrl-c.club/~lettuce
=> gemini://bbs.archaicbinary.net/atom.xml
=> gemini://snowcode.ovh/rss.xml
=> gemini://ainent.xyz/gemlog/index.gmi
=> gemini://phreedom.club/~tolstoevsky/glog/atom.xml
=> gemini://gems.geminet.org/avr/atom.xml
=> gemini://0x80.org/gemlog/atom.xml
=> gemini://gemini.rawles.net/blog/atom.xml
=> gemini://josias.dev/gemlog/feed.xml
=> gemini://sbg.one/SandboxGeneral/atom.xml
=> gemini://gemini.freeradical.zone/log/
=> gemini://gem.iwritethe.codes/posts/atom.xml
=> gemini://dira.cc/gemlog/atom.xml
=> gemini://figbert.com/log/atom.xml
=> gemini://lantashifiles.com/gemlog/entries/atom.xml
=> gemini://sl1200.dystopic.world/feed.gmi

Gemini/gemini-scripts-README.txt

# 2021-02-16
All these scripts are very specific to Techright's structure.  Though
ideas and components might be reusable elsewhere with modification.
Each involves /lots/ of scraping and are therefore inherently brittle.
They are run twice a day via a shell script in cron:
$ crontab -l | grep -E -v '^#'
01 */3 * * * /home/gemini/bin/gemini-cron-updater.sh
That script contains the work flow for all the steps needed to keep the
Gemini site up to date.  So reading it would be the place to start.
# gemini-fetch-urls-from-rss.pl
This gets an RSS feed and extracts the date.  RPiOIS has a quirk in regards
to /dev/stdout ownership, so that part needs repair.  However, the -w option
works.  This script is standalone but can pipe data to another script or
pass it via a file.  Probably this ought to be redone to use wget instead.
Example:
 ./gemini-fetch-urls-from-rss.pl http://techrights.org/feed/
Example:
 ./gemini-fetch-urls-from-rss.pl -d 2021-02-12 http://techrights.org/feed/
Example:
 ./gemini-fetch-urls-from-rss.pl -w tr.urls.txt -d 2021-02-12 \
	http://techrights.org/feed/
# gemini-fetch-web-page.pl
Reads in a URL and fetches them using wget.   wget is more flexible
than CPAN's LWP.  This script is not standalone, it calls the next script,
gemini-parse-html-to-gemini.pl using a temporary file to pass the data.
It has the option to convert some of the links to Gemini links.
Example:
 cat ~/bin/feeds.tr.2021.txt \
 | xargs ~/bin/gemini-fetch-web-page.pl -g -p -v -b /home/gemini/gemini/
# gemini-parse-html-to-gemini.pl
Reads a file containing XHTML and converts it to Gemini mark up, with
the expectation that it conforms to the structures used at Techrights
as of 2021.  This is a standalone script.
Example:
 ./gemini-parse-html-to-gemini.pl -w /dev/stdout -v sample.html
# gemini-inventory.pl
Traverses the file system and extracts titles from the Gemini articles.
The data is used to construct daily and monthly summaries.
Example:
 ./gemini-inventory.pl /home/gemini/gemini/2021/
## perl modules
XPath is used for most of the processing.
Cwd
English
File::Basename
File::Glob
File::Path
File::Temp
Getopt::Std
HTML::TreeBuilder::XPath
HTTP::Response::Encoding
LWP::UserAgent
Path::Iterator::Rule
POSIX
Time::ParseDate
Time::Piece
URI
utf8
XML::Feed
# gemini-bulletin-irc-update.sh
Creates Gemini indexes from the text bulletins.  The bulletins will
be read from the ~/gemini/tr_text_versioni/ directory.
# gemini-cron-updater.sh
A batch job to update starting by reading the RSS feed and finishing
with updating the indexes and bulletins.
# gemini-main-index-template.sh
A template for making the 'home page' for the Gemini site

Gemini/wget-tr-pages.sh

#!/bin/sh

# 2021-02-22        download TR back issue articles a year at a
#                time and then convert them from HTML to Gemini

year=$1
echo $year | sed -r '/^[0-9]{4}$/q; s/.*/0/'

if [ $year -eq 0 ]; then
        echo "Year missing"
        exit 1
fi

stop=$1
echo $stop | sed -r '/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/q; s/.*/0/'

target=/home/gemini
cache=${target}/X/
log=/tmp/wget.tr.log

# ---

PATH=/home/gemini/bin:/usr/local/bin:/usr/bin:/bin

test -f ${log} && rm -f ${log}

# the wget loops can take from 5 to 13 hours depending on
# the number of posts that year

for m in $(seq --format '%02.0f' 11 12)
do
        for d in $(seq --format '%02.0f' 1 31);
        do
                date -d "$year-$m-$d" > /dev/null || break
                wget \
                        --progress=dot \
                        --limit-rate=90k \
                        --wait=4 \
                        --random-wait \
                        --tries=22 \
                        --retry-on-http-error=500,503 \
                        --no-host-directories \
                        --directory-prefix=${cache} \
                        --recursive \
                        --level=3 \
                        --no-parent \
                        --accept '*.html' \
                        http://techrights.org/${year}/$m/$d/ \
                        | tee -a ${log}
        done
done

# find all the cached web pages and send them to the parser
# for conversion to Gemini format; use parallel to run several
# instances of the parser concurrently
time \
find ${cache} \
        -type f \
        -name '*.html' \
        -not -path '*/page/*' \
        -not -path '*/feed/*' \
        -print0 | \
parallel \
        --null \
        --jobs 6 gemini-fetch-web-page.pl \
                -p -b ${target} file://{} :::

# see also:
#         https://www.gnu.org/software/parallel/parallel_cheat.pdf

exit 0

Gemini/purge_logs.sh


# To protect people's privacy run near end of each month; don't delete everything for a number of reasons, including open files and spacing anomalies

echo 'Log purge of the following files:'
ls -la ../logs/gemini-log-2023-05*

rm -f ../logs/gemini-log-2023-05-3*
rm -f ../logs/gemini-log-2023-05-0*
rm -f ../logs/gemini-log-2023-05-1*
rm -f ../logs/gemini-log-2023-05-2*
echo  "C'est la vie"

Gemini/tr-archive-stats.sh

#!/bin/sh
# archive stats page
# will need to be extended at some point  to partition many pages

g=/home/gemini/techrights.org
d=$(date +"%Y-%m-%d")

set -e

cp ${g}/stats/index.gmi ${g}/stats/stats$(date -d ${d} +"-%Y-%m-%d").gmi

sed -i "1s|^|$(date +"# %d %m %Y Archive\n")|" \
    ${g}/stats/stats$(date -d ${d} +"-%Y-%m-%d").gmi

exit 0

Gemini/.directory-listing-ok

Gemini/gemini-fetch-web-page.pl

#!/usr/bin/perl -T

# 2021-02-17	Fetches HTML from URLs and
#		calls another script to convert to Gemini
#		Alpha Version
#		Reads the URLs from the command line

use utf8;
use Getopt::Std;
use Cwd qw(getcwd abs_path);
use URI;
use File::Path qw(make_path);
use File::Basename;
use File::Temp qw(tempfile);
use HTML::TreeBuilder::XPath;

use English;

use warnings;
use strict;

$OUTPUT_AUTOFLUSH = 1;

# untaint the $PATH
$ENV{'PATH'} = '/home/gemini/bin:/usr/local/bin:/usr/bin:/bin';

# path to the next script, it will convert XHTML to Gemini and save it
# my $parser = './gemini-parse-html-to-gemini.pl';
my $parser = 'gemini-parse-html-to-gemini.pl';

# command line option
our %opt;
getopts('b:ghmpv', \%opt);

&usage if ($opt{'h'});

# set the base working directory for resulting files,
# it will be created later on if necessary
my $dest_path;
if ($opt{'b'}) {
    $dest_path = $opt{'b'};
}

# sanity checking of destination path
unless($dest_path) {
    warn("Use -b to set a destination path.\n\n");
    &usage;
}
if ($dest_path =~ m{^/?.*?(?=\.\.)}) {
    die("Use an absolute path.\n\n");
} elsif ($dest_path =~ m{^(/[\w\-\=\%\.\/]+)}) {
    $dest_path = $1;
    $dest_path =~ s|([^/])$|$1/|;
} else {
    die("Wonky path: '$dest_path'\n\n");
}
$dest_path = abs_path($dest_path);
if( ! $dest_path){
    die("Bad destination path.\n\n");
} elsif (-d $dest_path && ! -w $dest_path) {
    warn("Path: '$dest_path'\n");
    die("Destination path is not writable: '$dest_path' \n\n");
} elsif (! -d $dest_path && ! $opt{'p'}) {
    warn("Path: '$dest_path'\n");
    die("Destination path doesn't exist.  Use -p also.\n\n");
}



# process the URLs passed as command line parameters
my @URL = ();
foreach my $url (@ARGV) {
    my $uri = URI->new($url);
    if (defined($uri->scheme)) {
	if ($uri->scheme eq 'http'
	    or $uri->scheme eq 'https'
	    or $uri->scheme eq 'file') {
	    push(@URL, $uri);
	}
    }
}

&usage if ($#URL < 0);

# process each URL, one at a time
foreach my $url (@URL) {
    if ($opt{'v'}) { print $url->canonical,qq(\n); }

    # get the document path from the URL
    my $path = $dest_path.'/'.$url->path;
    ( $path ) = ($path =~ m/^([\w\-\.\/\%]+)$/);    # untaint
    while ($path =~ s|//|/|g) { 1 }

    my $name = '';
    ($name, $path) = fileparse($path);
    if ($name) {
	$name =~ s/\.html$//;
	$name .= '.gmi';
	($name) = ($name =~ m/^([\w\-\.\/\%]+)$/);  # untaint
    } else {
	$name = 'index.gmi';
    }

    if($opt{'v'}) {
	print qq(Path = $path\n);
	print qq(Name = $name\n);
    }

    # use a temp file to get the XHTML over to the next script
    my $tmp = File::Temp->new( TEMPLATE => 'temp.XXXXX',
			       DIR      => '/tmp',
			       SUFFIX   => '.fetch.gemini.tmp',
			       UNLINK   => 1 );
    my $tmpfile = $tmp->filename;
    -f $tmpfile && unlink($tmpfile);	# clear the way for wget

    my $success = 0;
    if ($url->scheme eq 'http' or $url->scheme eq 'https') {
	# fetch URL and hold content in a variable
	$success = &fetch($url, $tmpfile);

	# if the page content and destination path exist, continue
	if ( $success && $path) {
	    # make the target directory if called for
	    unless(-d $path or ! $opt{'p'}) {
		if ($path =~ m{^(/[\w\-\=\%\.\/]+)}) {
		    $path = $1;
		}
		make_path($path,{mode=>0755})
		    or die("Could not create path '$path' : $!\n");
	    }

	    # parse temp file and write output to "$path$name"
	    my @args = ();
	    push(@args, '-g') if ($opt{'g'});	# tell it to convert links
	    my @cmd = ($parser, @args, '-w', "$path$name",  $tmpfile);
	    system(@cmd) == 0
		or die("system '@cmd' failed: $?\n");

	    # fetching videos is not done by default
	    if($opt{'m'}) {
		print qq(Fetching videos\n) if ($opt{'v'});
		$success = &extract_webm($url, $tmpfile, $path);
	    }

	    # close temp file, if UNLINK is set then it is deleted now
	    close($tmp);

	} else {
	    exit(1);
	}
    } elsif ($url->scheme eq 'file') {
	print qq(FILE:///\n) if ($opt{'v'});
	$success = &fetch_file($url, $tmpfile);
	$path = $url->path;
	# hard-coded /yyyy/mm/dd/ pattern ...
	if($path =~ m{^.*/(\d{4}/\d{2}/\d{2}/.*)/[^/]+\.html}) {
	    $path = $dest_path.'/'.$1;
	} else {
	    exit(0);
	}
	print qq(DEST_PATH=$path\n) if ($opt{'v'});

	# if the page content and destination path exist, continue
	if ( $success && $path) {
	    # make the target directory if called for
	    unless(-d $path or ! $opt{'p'}) {
		if ($path =~ m{^(/[\w\-\=\%\.\/]+)}) {
		    $path = $1;
		}
		make_path($path,{mode=>0755})
		    or die("Could not create path '$path' : $!\n");
	    }
	    # parse temp file and write output to "$path$name"
	    my @args = ();
	    push(@args, '-g') if ($opt{'g'});   # tell it to convert links
	    my @cmd = ($parser, @args, '-w', "$path/$name",  $tmpfile);
	    print qq(CMD=@cmd\n) if($opt{'v'});
	    system(@cmd) == 0
		or die("system '@cmd' failed: $?\n");

	    # fetching videos is not done

	    # close temp file, if UNLINK is set then it is deleted now
	    close($tmp);

	} else {
	    exit(1);
	}

    }

}


exit(0);

sub usage {
    print qq(Fetch web pages (from TechRights) for conversion to Gemini\n);
    $0 =~ s/^.*\///;
    print qq($0: [-hbmpv] URL [URL...]\n);
    print qq( -h	print this help text\n);
    print qq( -b	base working directory, default is current\n);
    print qq( -m	also fetch and store videos, default is not to\n);
    print qq( -p	create destination directories\n);
    print qq( -v	increase message verbosity\n);
    exit(1);
}

sub fetch_file {
    my ( $uri, $filename ) = ( @_ );
    my $path = $uri->path;	# works only with absolute paths
    print qq(SRC FILE=$path\n) if ($opt{'v'});
    unless(-f $path && -r $path){
	warn("File '$path' does not exist or is unreadable!\n");
	return(0);
    }
    my $result = system('cp', $path, $filename);
    if($result) {
	die("File copy failed,\n");
    }
    return(1);
}

sub fetch {
    my ( $uri, $filename ) = ( @_ );

    # wget is more flexible than LWP,
    # especially with large files like the videos
    my @args=(
	'--quiet',
	'--user-agent=TechRights-Gemini-Bot/0.1',
	'--no-directories',
	'--no-clobber',
	'--retry-on-http-error=500,503',
	'--tries=25',
	'--waitretry=8',
	"--output-document=$filename"
	);

    my $url = $uri->canonical;
    print qq(URI Canonical = ), $url,qq(\n) if ($opt{'v'});
    $url =~ s/\#.*$//;
    $url =~ s/\?.*$//;
    unless (( $url ) = ( $url =~ m/^([\w\.\-\%\/\:]+)$/ )) {
	print qq(WrongURL = $uri->canonical\n);
	return(0);
    }
    print qq(Fetching $url\n) if ($opt{'v'});
    my @cmd = ('wget', @args, $url);
    my $result = system(@cmd);
    if($result == 256) {
	warn("File '$filename' already exists!\n");
    } elsif ($result) {
	die("system '@cmd' failed: $?\n");
    }

    return(1);
}

sub extract_webm {
    my ($base, $tmpfile, $path) = (@_);

    my $xhtml = HTML::TreeBuilder::XPath->new;
    $xhtml->implicit_tags(1);
    $xhtml->no_space_compacting(0);

    $xhtml->parse_file($tmpfile);

    my %videos = ();
    my %ok_hosts = (
	'www.techrights.org' => 1,
	'techrights.org' => 1,
	);

    for my $a ($xhtml->findnodes('//a[contains(@href,".webm")]')) {
	my $url = URI->new($a->attr('href'));
	$url = $url->abs($base);	# convert relative links
	if (defined($url->host) && $ok_hosts{$url->host}) {
	    $videos{$url->canonical} = $url;
	}
    }

    foreach my $vurl (sort values %videos) {
	# get the document path from the URL
	my $path = $dest_path.'/'.$vurl->path;
	( $path ) = ($path =~ m/^([\w\-\.\/\%]+)$/);    # untaint
	while ($path =~ s|//|/|g) { 1 }

	my $name = '';
	($name, $path) = fileparse($path);
	if ($name =~ m/\.webm/) {
	    ($name) = ($name =~ m/^([\w\-\.\/\%]+)$/);  # untaint
	} else {
	    return(0);
	}
	if (! -d $path and $opt{'p'}) {
	    if ($path =~ m{^(/[\w\-\=\%\.\/]+)}) {
		$path = $1;
	    }

	    make_path($path,{mode=>0755})
		or die("Could not create path '$path' : $!\n");
	} elsif (! -d $path) {
	    die("Path '$path' is missing!\n");
	}

	&fetch($vurl, "$path$name");
    }
}

Gemini/gemini-bulletin-irc-update.sh

#!/bin/bash

# 2021-02-14	CreateGemini indexes for various IRC logs
# for History see Git archive
#

p=/home/gemini/techrights.org;	# path to working directory
limit=5;			# cycle back this many months

# #####
# main body of program

PATH=/usr/local/bin:/usr/bin:/bin

set -e

cd $p

now=$(date +"%Y-%m-%d")

# construct Gemini indexes for Bulletins
m=0
while test $m -lt $limit
do
	dd=$(date +"%Y-%m" -d "$now -$m month")
	bb=$(date +"%Y/%m" -d "$now -$m month")
	test -e ./bulletins/$bb/ || mkdir -p ./bulletins/$bb/
	cp ~gemini/bulletin-menu.txt ./bulletins/$bb/index.gmi
	find ./tr_text_version -name "techrights-$dd*"  -print | sort \
	    | sed -re 's|^\.([^-]+)-(.*).txt|=> \1-\2.txt Techrights \2|;' \
	    >> ./bulletins/$bb/index.gmi
	date -u +"%nUpdated %F %H:%M UTC%n" >> ./bulletins/$bb/index.gmi
	cat ~gemini/logo.txt >> ./bulletins/$bb/index.gmi
	m=$((m+1))
done

# construct Gemini indexes for IRC logs
m=0
while test $m -lt $limit
do
	mm=$(date +"%m" -d "$now -$m month")
	yy=$(date +"%y" -d "$now -$m month")
	yyyy=$(date +"%Y" -d "$now -$m month")
	test -e ./irc/$yyyy/$mm/ || mkdir -p ./irc/$yyyy/$mm/
	cp ~gemini/irc-menu.txt ./irc/$yyyy/$mm/index.gmi

	echo '## GemText Version'  >> ./irc/$yyyy/$mm/index.gmi
        find ./irc-gmi -name "*irc*techrights*$mm$yy.gmi" -print \
            | sort \
            | sed -r -e "s|^\.(.*)-(..)(.*)$|=> \1-\2\3 Gemini/GemText IRC Logs for Techrights $yyyy-$mm-\2|" \
            >> ./irc/$yyyy/$mm/index.gmi

        echo '## Plain Text Version'  >> ./irc/$yyyy/$mm/index.gmi
        find ./tr_text_version -name "*irc*techrights*$mm$yy.txt" -print \
            | sort \
            | sed -r -e "s|^\.(.*)-(..)(.*)$|=> \1-\2\3 Plain Text IRC Logs for Techrights $yyyy-$mm-\2|" \
            >> ./irc/$yyyy/$mm/index.gmi

	date -u +"%nUpdated %F %H:%M UTC%n" >> ./irc/$yyyy/$mm/index.gmi
	cat ~gemini/logo.txt >> ./irc/$yyyy/$mm/index.gmi

	m=$((m+1))
done

# construct Gemini indexes for TR Social IRC logs
m=0
while test $m -lt $limit
do
	mm=$(date +"%m" -d "$now -$m month")
	yy=$(date +"%y" -d "$now -$m month")
	yyyy=$(date +"%Y" -d "$now -$m month")
	test -e ./social/$yyyy/$mm/ || mkdir -p ./social/$yyyy/$mm/
	cp ~gemini/irc-menu.txt ./social/$yyyy/$mm/index.gmi

        echo '## GemText Version'  >> ./social/$yyyy/$mm/index.gmi
        find ./irc-gmi -name "*social*$mm$yy.gmi" -print \
            | sort \
            | sed -r -e "s|^\.(.*)-(..)(.*)$|=> \1-\2\3 Gemini/GemText IRC Logs for Techrights Social $yyyy-$mm-\2|" \
            >> ./social/$yyyy/$mm/index.gmi

        echo '## Plain Text Version'  >> ./social/$yyyy/$mm/index.gmi
	find ./tr_text_version -name "*social*$mm$yy.txt" -print \
	    | sort \
	    | sed -r -e "s|^\.(.*)-(..)(.*)$|=> \1-\2\3 Plain Text IRC Logs for Techrights Social $yyyy-$mm-\2|" \
	    >> ./social/$yyyy/$mm/index.gmi
	date -u +"%nUpdated %F %H:%M UTC%n" >> ./social/$yyyy/$mm/index.gmi
	cat ~gemini/logo.txt >> ./social/$yyyy/$mm/index.gmi

	m=$((m+1))
done

# construct Gemini indexes for TR TechBytes IRC logs
m=0
while test $m -lt $limit
do
	mm=$(date +"%m" -d "$now -$m month")
	yy=$(date +"%y" -d "$now -$m month")
	yyyy=$(date +"%Y" -d "$now -$m month")
	test -e ./techbytes/$yyyy/$mm/ || mkdir -p ./techbytes/$yyyy/$mm/
	cp ~gemini/irc-menu.txt ./techbytes/$yyyy/$mm/index.gmi

        echo '## GemText Version'  >> ./techbytes/$yyyy/$mm/index.gmi
        find ./irc-gmi -name "*techbytes*$mm$yy.gmi" -print \
            | sort \
            | sed -r -e "s|^\.(.*)-(..)(.*)$|=> \1-\2\3 Gemini/GemText IRC Logs for Techrights TechBytes $yyyy-$mm-\2|" \
            >> ./techbytes/$yyyy/$mm/index.gmi

        echo '## Plain Text Version'  >> ./techbytes/$yyyy/$mm/index.gmi
	find ./tr_text_version -name "*techbytes*$mm$yy.txt" -print \
	    | sort \
	    | sed -r -e "s|^\.(.*)-(..)(.*)$|=> \1-\2\3 Plain Text IRC Logs for Techrights TechBytes $yyyy-$mm-\2|" \
	    >> ./techbytes/$yyyy/$mm/index.gmi

	date -u +"%nUpdated %F %H:%M UTC%n" >> ./techbytes/$yyyy/$mm/index.gmi
	cat ~gemini/logo.txt >> ./techbytes/$yyyy/$mm/index.gmi

	m=$((m+1))
done

# construct Gemini indexes for TR BoycottNovell IRC logs
m=0
while test $m -lt $limit
do
	mm=$(date +"%m" -d "$now -$m month")
	yy=$(date +"%y" -d "$now -$m month")
	yyyy=$(date +"%Y" -d "$now -$m month")
	test -e ./boycottnovell/$yyyy/$mm/ || mkdir -p ./boycottnovell/$yyyy/$mm/
	cp ~gemini/irc-menu.txt ./boycottnovell/$yyyy/$mm/index.gmi
	# find ./tr_text_version -name "irc-log-*$mm$yy.txt" -print \

        echo '## GemText Version'  >> ./boycottnovell/$yyyy/$mm/index.gmi
        find ./irc-gmi -regextype posix-basic \
            -regex ".*/irc-log-[0-9]\{2\}$mm$yy.gmi\$" -print \
            | sort \
            | sed -r -e "s|^\.(.*)-(..)(.*)$|=> \1-\2\3 Gemini/GemText IRC Logs for Techrights BoycottNovell $yyyy-$mm-\2|" \
            >> ./boycottnovell/$yyyy/$mm/index.gmi

        echo '## Plain Text Version'  >> ./boycottnovell/$yyyy/$mm/index.gmi
	find ./tr_text_version -regextype posix-basic \
	    -regex ".*/irc-log-[0-9]\{2\}$mm$yy.txt\$" -print \
	    | sort \
	    | sed -r -e "s|^\.(.*)-(..)(.*)$|=> \1-\2\3 Plain Text IRC Logs for Techrights BoycottNovell $yyyy-$mm-\2|" \
	    >> ./boycottnovell/$yyyy/$mm/index.gmi


	date -u +"%nUpdated %F %H:%M UTC%n" \
		>> ./boycottnovell/$yyyy/$mm/index.gmi
	cat ~gemini/logo.txt \
		>> ./boycottnovell/$yyyy/$mm/index.gmi
	m=$((m+1))
done

cd $p/irc/
cp ~gemini/irc-menu.txt index.gmi
echo '## Logs for #techrights at irc.techrights.org' >>  index.gmi
echo  >>  index.gmi
find .  -mindepth 2 -maxdepth 2 -type d \
	| sort -r  \
	| cut -b 3-100 \
	| while read -r line; do \
		echo -n "=> $line/index.gmi " & sed "s/\/index.gmi//" \
			<<< "$line"
	done >>  index.gmi
date -u +"%nUpdated %F %H:%M UTC%n" >> index.gmi
cat ~gemini/logo.txt >> index.gmi

cd $p/social/
cp ~gemini/irc-menu.txt index.gmi
echo '## Logs for #boycottnovell-social at irc.techrights.org' >>  index.gmi
echo  >>  index.gmi
find .  -mindepth 2 -maxdepth 2 -type d \
	| sort -r  \
	| cut -b 3-100 \
	| while read -r line; do \
		echo -n "=> $line/index.gmi  " & sed "s/\/index.gmi//" \
			<<< "$line"
	done >>  index.gmi
date -u +"%nUpdated %F %H:%M UTC%n" >> index.gmi
cat ~gemini/logo.txt >> index.gmi

cd $p/techbytes/
cp ~gemini/irc-menu.txt index.gmi
echo '## Logs for #techbytes at irc.techrights.org' >>  index.gmi
echo  >>  index.gmi
find .  -mindepth 2 -maxdepth 2 -type d \
	| sort -r  \
	| cut -b 3-100 \
	| while read -r line; do \
		echo -n "=> $line/index.gmi  " & sed "s/\/index.gmi//" \
			<<< "$line"
	done >>  index.gmi
date -u +"%nUpdated %F %H:%M UTC%n" >> index.gmi
cat ~gemini/logo.txt >> index.gmi

cd $p/boycottnovell/
cp ~gemini/irc-menu.txt index.gmi
echo '## Logs for #boycottnovell at irc.techrights.org' >>  index.gmi
echo  >>  index.gmi
find .  -mindepth 2 -maxdepth 2 -type d \
	| sort -r  \
	| cut -b 3-100 \
	| while read -r line; do \
		echo -n "=> $line/index.gmi  " & sed "s/\/index.gmi//" \
			<<< "$line"
	done >>  index.gmi
date -u +"%nUpdated %F %H:%M UTC%n" >> index.gmi
cat ~gemini/logo.txt >> index.gmi

exit 0

Gemini/gemini-make-feed-wrapper.sh

#!/bin/sh

PATH=/home/gemini/bin:/usr/local/bin:/usr/bin:/bin

# percent signs are processed in crontabs so this wrapper is needed

gemini-make-feed.pl \
        -r /home/gemini/gemini \
        -o /home/gemini/gemini/feed.xml \
        /home/gemini/gemini/$(date +"%Y/%m")/ \
> /home/gemini/logs/make-feed.log

exit 0

Gemini/irc.gmi

Welcome to Techrights IRC section

=> / Back to index

# IRC Introduction

Techrights has its own IRC network.

=> about/ To access our network see details in this page

# IRC Logs in Techrights

=> chat/index.gmi ▢ - Latest chat logs for #techrights (updated every 5-10 minutes)

### Techrights Archive

=> irc/index.gmi ㏒ - Logs for #techrights

### Social Archive

=> social/index.gmi ㏒ - Logs for #boycottnovell-social

### Techbytes Archive

=> techbytes/index.gmi ㏒ - Logs for #techbytes

### BoycottNovell Archive

=> boycottnovell/index.gmi ㏒ - Logs for #boycottnovell

=> /logo/logo.png Site logo (if your Gemini client supports that)

### ➮ Sharing is caring. Content is available under CC-BY-SA. ⟲

Gemini/agate-tcpdump-logger.sh

#!/bin/sh

# 2022-01-22

PATH=/usr/sbin:/usr/bin:/sbin:/bin

today=$(date +"%F")
echo '\n----------------------------------------------------------------' \
>> /home/gemini/logs/gemini-log-${today}.log

date +"Restarting logging at %F %T%n" \
>> /home/gemini/logs/gemini-log-${today}.log

# use wlan0 below if the server uses Wi-Fi

stdbuf -oL tcpdump -ttttqpli eth0 'inbound and port 1965
        and not src net 192.168.4.0/24
        and tcp[tcpflags] & tcp-syn != 0' \
| perl -a -n -e '
        $|=1;   # autoflush buffer
        $d=$F[0]; $t=$F[1]; $h=$F[3];
        $t=~s/\.[0-9]+$//; $h=~s/\.\d+$//;
        # print STDERR qq($d $t $h\n);
        print qq($d $t $h\n);
        ' \
>> /home/gemini/logs/gemini-log-${today}.log

exit 0

Gemini/RPi-Manchester/show-new-visitors-count.sh

#!/bin/sh

# 2021-02-27 

PATH=/usr/bin:/bin

set -e

awk '$3 {a[$3]++} END{ for (b in a) {print a[b],b}}' OFS="\t" \
	/home/gemini/logs/gemini-log-$(date +"%F").log \
| sort -k1,1n -k2,2

exit 0

Gemini/RPi-Manchester/tcpdump-logger.sh

#!/bin/sh

PATH=/usr/sbin:/usr/bin:/sbin:/bin

now=$(date +"%F")
echo '----------------------------------------------------------------------------' >> /home/gemini/logs/gemini-log-${now}.log
echo -n 'Restarting logging at ' >> /home/gemini/logs/gemini-log-${now}.log
date >> /home/gemini/logs/gemini-log-${now}.log
echo '' >> /home/gemini/logs/gemini-log-${now}.log

tcpdump \
	-q \
        -p \
        -l \
        -tttt \
        -i wlan0 \
        'not src net 192.168.1.0/24 and dst net 192.168.1.0/24
		and tcp[tcpflags] & (tcp-syn) != 0 and port 1965' \
| awk '{
        sub(/\.[0-9]+$/,"",$2); \
        sub(/\.[0-9]+$/,"",$4); \
        print $1, $2, $4; \
        fflush();\
        }' >> /home/gemini/logs/gemini-log-${now}.log

Gemini/RPi-Manchester/gemini-tcpdump-logger.service

[Unit]
Description=Use tcpdump to log contavts to the Gemini server
After=network.target

[Service]
User=root
Type=exec
ExecStart=/usr/local/sbin/tcpdump-logger.sh
ExecReload=/bin/kill -HUP $MAINPID
KillMode=control-group
Restart=on-failure
RestartPreventExitStatus=255
RestartSec=5s

[Install]
WantedBy=multi-user.target

Gemini/RPi-Manchester/.directory-listing-ok

Gemini/RPi-Manchester/show-new-visitors.sh

#!/bin/sh

# 2021-02-13
# 2021-02-27 updated

# tail -n 100 -f log.txt \
# | grep -v host81 \
# | grep -v roibng \
# | grep -v '\-\-'

# host81-154-168-60.range81-154.btce:44808 <= 0b 739b 462b 924B

# [2021-02-26 04:29:59] v2202102141844143923.supersrv.de:53510 <= 0b 0b 266b 1.30KB

PATH=/usr/bin:/bin

# tail -f /home/gemini/logs/gemini-$(date +"%F").log \ 

# exit 0

tail -n 500 -f /home/gemini/logs/gemini-log-$(date +"%F").log \
 | awk '($5!="0b" || $6!="0b" || $7!="0b" || $8!="0b") \
	&& $3!~/roibng/ \
	&& $3!~/btce/ \
	&& $3~/[a-z0-9]/ { \
		sub(/:.*$/, "", $3); \
		print $1 " " $2, $3; \
	}'

Gemini/tr-gemini-latest-videos.sh

#!/bin/sh

# update latest video gallery page

PATH=/home/gemini/bin:/usr/local/bin:/usr/bin:/bin
d=/var/www/techrights.org/htdocs
g=/home/gemini/techrights.org

cat $d/videos/index.html | tr-gemini-latest-videos.pl > $g/videos/index.gmi

exit 0

Gemini/archive-stats.sh

#!/bin/sh
# archive stats page
# will need to be extended at some point  to partition many pages

 cp  /home/gemini/gemini/stats/index.gmi  /home/gemini/gemini/stats/stats$(date +"-%Y-%m-%d").gmi  &&  sed -i "1s|^|$(date +"# %d %m %Y Archive\n")|" /home/gemini/gemini/stats/stats$(date +"-%Y-%m-%d").gmi

Gemini/gemini-ping-roy.sh

#!/bin/sh

# 2020-12-25

PATH=/home/gemini/bin:/usr/local/bin:/usr/bin:/bin

if test "x$1" = "xbright";
then
	timeout 9 run-blinkt.py flashed_bar 250 255 255 0.0001
else
	timeout 9 run-blinkt.py flashed_bar 250 128 128 0.0001
fi

exit 0

Gemini/gemini-latest-videos.sh

#!/bin/sh

# 2021-09-17
# update latest video gallery page

PATH=/home/gemini/bin:/usr/local/bin:/usr/bin:/bin

sudo /usr/local/sbin/tc-shaper-v2021-Nov.sh
wget -qO- --tries=21 --waitretry=10 http://techrights.org/videos/ \
| gemini-latest-videos.pl > ~/gemini/latest-videos/index.gmi
sudo /usr/local/sbin/tc-shaper-v2.sh

exit 0

Gemini/tr-gemini-git-sloc.sh

#!/bin/sh

# 2021-10-09

PATH=/usr/local/bin:/usr/bin:/bin

d=/home/gemini/techrights.org/git/sloc.gmi
s=/home/gemini/techrights.org/git/tr-git/

echo "# SLOC stats $(date +'%F')\n" > ${d}

echo "Source Lines Of Code (SLOC) it not a useful metric and provideded \
only for amusement.\n" >> ${d}

sloccount ${s} 2>/dev/null \
| sed -n -r -e '
	/^SLOC/,/^$/ {s/^SLOC./# SLOC  /; s/SLOC-/   &/;p};
	/^Totals/,/^$/ {s/^Totals/# &/;p};
	/^Total / {p;q}' \
>> ${d}

cat << EOT >> ${d}

## Licence

GNU Affero General Public License (AGPLv3) unless stated otherwise, e.g. Public Domain

=>	/	back to Techrights (Main Index)
EOT

Gemini/gemini-latest-videos.pl

#!/usr/bin/perl

# 2021-09-17
# update latest video gallery page

use HTML::TreeBuilder::XPath;
use File::Basename;
use JSON;

print "# Latest Techrights Videos\n\n";
print "Most recent shown first (the list below is limited to past 7 days)\n\n";

my %videos   = &xhtml_video_links('-');
my %metadata = &fetch_metadata(%videos);

foreach my $key (sort {$a<=>$b} keys %metadata) {
    print "=> ";
    print $metadata{$key}{'link'}," ";

    print "↺ ";
    printf ("%2d ",$key);
    if (exists($metadata{$key}{'title'})) {
	print $metadata{$key}{'title'};
	if(exists($metadata{$key}{'date'})){
	    print " (",$metadata{$key}{'date'},")";
	}
    } else {
	print $metadata{$key}{'file'};
    }
    print "\n";
}

print "\n=> /videos/ Show videos archive\n";
print "=> / Back to homepage\n";

exit(0);

sub xhtml_video_links{
    my ($file)= (@_);

    my $is_stdin = 0;
    my $input;

    if ($file eq '-') {
           $input = *STDIN;
           $is_stdin++;
    } else {
           # force input to be read in as UTF-8
           open ($input, "<:utf8", $file)
           or die("Could not open file '$file' : error: $!\n");
    }

    my $xhtml = HTML::TreeBuilder::XPath->new;
    $xhtml->implicit_tags(1);
    $xhtml->no_space_compacting(0);

    $xhtml->parse_file($input)
    or die("Could not parse '$file' : $!\n");

    my %videos = ();
    my $listpos = 0; # reset position at 0
    for my $l ($xhtml->findnodes_as_strings('//a[@target="video"]/@href') ) {
           my $link = qq(http://techrights.org/videos/).$l;
           my $vid  = basename($link);
           $listpos++;
           $videos{$listpos}{'link'} = $link;
	   $videos{$listpos}{'file'} = $vid;
    }
    $xhtml->destroy;

    unless($is_stdin) {
        close($input);
    }

    return(%videos);
}

sub fetch_metadata() {
    my (%metadata) = (@_);

    foreach my $listpos (keys %metadata) {
	my @cmd = ("ffprobe",
		   "-v", "panic", "-of", "json", "-show_format",
		   $metadata{$listpos}{'link'});

	open(my $json, "-|", @cmd)
	    or die("Could not open pipe '@cmd' : $!\m");

	my @text = ();
	while (my $j = <$json>) {
	    push (@text, $j);
	}

	close($json);

	my $t = join("", @text);

	my $d = decode_json(join("",@text));

	# print Dumper($d),"\n";

	if ($d->{'format'}->{'tags'}->{'title'}) {
	    $metadata{$listpos}{'title'} = $d->{'format'}->{'tags'}->{'title'};
	}

	if ($d->{'format'}->{'tags'}->{'DATE'}) {
	    $metadata{$listpos}{'date'}  = $d->{'format'}->{'tags'}->{'DATE'};
	}

	# print $metadata{$listpos}{'link'},"\n";
	# print $metadata{$listpos}{'file'},"\n\n";
    }

    return(%metadata);
}

Gemini/gemini-parse-html-to-gemini.pl

#!/usr/bin/perl

# 2021-01-11        read HTML from a file and convert it to Gemini
#                not-generic, for TR only

# 2021-02-21        Proof-of-concept prototype
#                not suitable for production

use utf8;
use Getopt::Std;
use File::Glob ':bsd_glob';
use HTML::TreeBuilder::XPath;
use URI;
binmode *STDOUT, ':utf8';

use English;

use warnings;
use strict;

$OUTPUT_AUTOFLUSH = 1;

our %opt;

getopts('ghmvw:', \%opt);

&usage if ($opt{'h'});

# iterate through any parmeters passed on the command line,
# treat as file names and allow globbing
my @filenames;
while (my $file = shift) {
    my @files = bsd_glob($file);
    foreach my $f (@files) {
        push(@filenames, $f);
        if($opt{'v'}) { print qq(F=$f\n); }
    }
}

my $outfile = $opt{'w'} || '';
( $outfile ) = ( $outfile =~ m/^([\w\-\.\/]+)$/ );

# quit if no files were listed
&usage if($#filenames < 0);

# process each file, skipping turd files, hidden files, and temp files
while (my $infile = shift(@filenames)) {
    next if ($infile =~ m/~$/);
    next if ($infile =~ m/^\.\.?(?!\/)/);
    next if ($infile =~ m/^#/);

    my $result = &xhtml_to_gemini($infile) || exit(1);
    if ($outfile) {
        my ($dir) = ($outfile =~ m{^(.*/)[^/]+$});
        if (! -d $dir) {
            die("Directory '$dir' does not exist.\n");
        }
        if (! -w $dir) {
            die("Directory '$dir' is not writable.\n");
        }
        open(my $o, '>:utf8', $outfile)
            or die("Could not open '$outfile' for writing: $!\n");

        if ($opt{'v'}) { print qq(Writing to "$outfile"\n); }
        print $o $result;
        print $o qq(\n),qq(-)x10,qq(\n);
        print $o qq(=>\t/\tTechrights\n);
        print $o qq(➮ Sharing is caring.  );
        print $o qq(Content is available under CC-BY-SA.);

        close($o);
    } else {
        print $result;
        print qq(\n),qq(-)x10,qq(\n);
        print qq(=>\t/\tTechrights\n);
        print qq(➮ Sharing is caring.  );
        print qq(Content is available under CC-BY-SA.\n);
    }
}

exit(0);

sub usage {
    print qq(Read pages via files or else stdin and convert them\n);
    print qq(from XHTML to Gemini.\n);
    $0 =~ s/^.*\///;
    print qq($0: xhtml_file [xhtml_file...]\n);
    print qq( -g        convert internal links to Gemini\n);
    print qq( -m        convert internal video links, default not to\n);
    print qq( -w file        save output to the given file\n);
    print qq( -h        this help message\n);
    print qq( -v        increase verbosity and debugging info\n);
    exit(1);
}

sub xhtml_to_gemini{
    my ($file)= (@_);

    # force input to be read in as UTF-8
    my $input;
    open ($input, "<:utf8", $file)
        or die("Could not open file '$file' : error: $!\n");

    # parse UTF-8
    my $xhtml = HTML::TreeBuilder::XPath->new;
    $xhtml->implicit_tags(1);
    $xhtml->no_space_compacting(0);

    $xhtml->parse_file($input)
        or die("Could not parse '$file' : $!\n");

    my $result = '';
    if (my $l = $xhtml->findnodes_as_string('//h2/a') ) {
        # this pattern is weird
        if($l =~ m/>Links\D+\d+\D\d+\D\d+\D/) {
            print qq(DAILY LINKS\n) if ($opt{'v'});
            $result = &daily_links_page($xhtml);
        } elsif($l =~ m/>Leftover Links/) {
            print qq(LEFTOVER LINKS\n) if ($opt{'v'});
            $result = &daily_links_page($xhtml);
        } elsif($l =~ m/>Gemini Links/) {
            print qq(GEMINI LINKS\n) if ($opt{'v'});
            $result = &daily_links_page($xhtml);
        } else {
            print qq(NORMAL PAGES\n) if ($opt{'v'});
            $result = &normal_page($xhtml);
        }
    }
    $xhtml->destroy;
    close($input);
    return($result);
}

sub normal_page {
    my ($xhtml) = (@_);

    my %prefix = (
        'h1' => "# ● ",
        'h2' => "## ●● ",
        'h3' => "### ●●● ",
        'h4' => "### ●●●● ",
        'h5' => "### ●●●● ",
        'h6' => "### ●●●● ",
        );
    if ($opt{'g'}) {
        $xhtml = &links2gemini($xhtml);
    }

    # print qq(Parsing file\n);
    my $result;
    for my $post ($xhtml->findnodes('//div[contains(@class,"post")]')){

	# print STDERR "parsing post\n";
        foreach my $hn (1 .. 5) {
            # format headings
            $hn = qq(h$hn);
            for my $heading ($post->findnodes(".//$hn")) {
                my $h = "";
		if (defined($prefix{$hn})) {
		    $h .= $prefix{$hn};
		}
                $h = qq(\n\n).$h.$heading->as_text.qq(\n\n);

                my $tmp = HTML::Element->new('~literal');
		$tmp->push_content($h);
                $heading->replace_with($tmp);
            }
        }

        for my $pq ($post->findnodes('.//span[@class="pullQuote"]')){
            my $text = $pq->as_text;
            $text =~ s/\s+/ /gm;
            $text =~ s/\n+/ /gm;

            my $tmp = HTML::Element->new('span');
            $tmp->push_content("\n\n> " . $text . "\n\n");
            $pq->replace_with($tmp);
        }

        for my $pp ($post->findnodes('./p[@class="meta"]')) {
            # keep the text but ditch the hyperlinks
            my $text = $pp->as_text;
            $text =~ s/\s+/ /gm;
            $text =~ s/\n+/ /gm;

            my $tmp =  HTML::Element->new('p');
            $tmp->push_content($text);
            $pp->replace_with($tmp);
        }

        for my $ol ($post->findnodes('./ol')) {
            my $item = 1;
            for my $li ($ol->findnodes('./li')) {
                my $href ='';
                my $new_li = HTML::Element->new('~literal');
		$new_li->push_content($li->as_text."\n");
                for my $a ($li->findnodes('./a')) {
                    my $href = $a->attr('href');

                    $href=~ s{https?://techrights.org(/\d{1,4}/\d{1,2}/\d{1,2}/)}
                    {$1}x;
                    $href=~ s{gemini://gemini.techrights.org(/\d{1,4}/\d{1,2}/\d{1,2}/)}
                    {$1}x;

                    $href=~ s/\s/%20/g;

                    next if ($href=~ m/^#/);
                    next if ($href=~ m/\.jpe?g$/);
                    next if ($href=~ m/\.png$/);
                    next if ($href=~ m/\.gif$/);

                    $new_li->push_content("\n=>\t".$href."\t".
                                          $item++." ".$a->as_text."\n");
                }

                $li->replace_with($new_li);
            }
        }

        for my $ul ($post->findnodes('./ul[@class="irc-gemini"]')) {
            for my $li ($ul->findnodes('./li')) {
                my $href ='';
                my $new_li = HTML::Element->new('li');
		$new_li->push_content($li->as_text."\n");

                for my $a ($li->findnodes('./a')) {
                    $href = $a->attr('href');

                    # because the direcories on Gemini and WWW are different,
                    # the path must change so that Gemini can point
                    # to gemtext instead of plain text
                    if( $href=~
                        s{^(gemini://gemini.techrights.org)/tr_text_version/}
                        {$1/irc-gmi/}x ) {

                        $href =~ s{/(irc-log[^/]+)\.txt$} {/$1.gmi}x;
                        $a->delete_content();
                        $a->push_content($href);
                    }
                    $new_li->push_content("\n=>\t".$href."\t ".
                                          $a->as_text."\n");
                }
                $li->replace_with($new_li);
            }
        }

        for my $quote ($post->findnodes('./blockquote')){
            $quote->unshift_content("\n> ");
            $quote->push_content("\n> ");
            for my $pp ($quote->findnodes("./p")) {
                $pp->unshift_content("\n> \n> ");
            }
	    # treat pre within a blockquote as a blockquote
            for my $pp ($quote->findnodes("./pre")) {
		my $text = $pp->as_text;
		$text =~ s/\n/\n> /gm;
		my $tmp = HTML::Element->new('~literal');
                $tmp->push_content($text);
                $pp->replace_with($tmp);
            }
        }

        for my $pre ($post->findnodes('./pre')){
	    my $text = $pre->as_text;
	    chomp($text);
	    $text = '``` ' . $text;
	    $text =~ s/\n/\n``` /gm;
	    my $tmp = HTML::Element->new('~literal');
	    $tmp->push_content($text.qq(\n\n));
	    $pre->replace_with($tmp);
        }

        for my $pp ($post->findnodes("./p")) {
            # kludge: HTML5 video element is not recognized by parser
            next unless ($pp->as_text =~ m/Video download link/
                || $pp->as_text =~ m/Reprinted/);
            my @anchors=();
            for my $a ($pp->findnodes('.//a[@href]')) {
                my $href = $a->attr('href');
                next if ($href =~ m/^#/);
                $href =~ s/\s/%20/g;
                my $t = $a->as_text;
                if(0 && $opt{'m'}) {
                    # convert video link to gemini local,
                    # presumably it was already downloaded by now
                    $href =~ s{^https?://techrights.org/}
                    {gemini://gemini.techrights.org}x;
                } else {
                    # don't convert the video link, leave it alone
                    # $href =~ s{^https?://techrights.org/} {/}x;
                    $t = '↺ '.$t;
                }
                push (@anchors, "=>\t".$href."\t".$t);
            }
            my $d;
            if($#anchors >= 0) {
                $d = $pp->as_text.qq(\n\n)
                    .join("\n", @anchors ).qq(\n\n);
            } else {
                $d = $pp->as_text.qq(\n\n);
            }

            my $tmp =  HTML::Element->new('~literal', 'text' => $d);
            $pp->replace_with($tmp);
        }
        for my $br ($post->findnodes("./br")) {
            my $tmp =  HTML::Element->new('~literal', 'text' => "\n\n");
            $br->replace_with($tmp);
        }
        for my $pp ($post->findnodes("./p")) {
            my @anchors=();
            for my $a ($pp->findnodes('.//a[@href]')) {
                my $href = $a->attr('href');

                $href=~ s{https?://techrights.org(/\d{1,4}/\d{1,2}/\d{1,2}/)}
                {$1}x;
                $href=~ s{gemini://gemini.techrights.org(/\d{1,4}/\d{1,2}/\d{1,2}/)}
                {$1}x;

                $href=~ s/\s/%20/g;
                if(my ($img) = $a->findnodes('.//img')) {
                    my $alt = $img->attr('alt') || 'unknown';
                    # lll
                    $a->detach_content;
                    $a->push_content('Image: '.$alt);
                }

                next if ($href=~ m/^#/);
                next if ($href=~ m/\.jpe?g$/);
                next if ($href=~ m/\.png$/);
                next if ($href=~ m/\.gif$/);

                unless($href=~m/^gemini/ || $href=~m/^\//) {
                    $href = $href.' ↺';
                }
                push (@anchors, "=>\t".$href." ".$a->as_text);
            }
            for my $img ($pp->findnodes('./img[@alt]')) {
                my $alt = $img->attr('alt') || 'unknown';

                chomp($alt);

                $img->detach_content;
                $img->push_content('> Image: '.$alt."\n\n");
            }
            my $p;
            if($#anchors >= 0) {
                $p = $pp->as_text.qq(\n\n). join("\n", @anchors ).qq(\n\n);
            } else {
                $p = $pp->as_text.qq(\n\n);
            }

            my $tmp =  HTML::Element->new('~literal', 'text' => $p);
            $pp->replace_with($tmp);
        }
        for my $node ($post->findnodes('./*')){
            my $n = $node->as_text.qq(\n\n);
            my $tmp =  HTML::Element->new('~literal', 'text' => $n);
            $node->replace_with($tmp);
        }

	# suppress warnings for $post->as_XML line
	local $SIG{__WARN__} = sub {
	    # here we get the warning
	    my $warning = shift;
	    if ($warning =~ m{join or string at
                              /usr/share/perl5/HTML/Element.pm
                              line 1184.}x) {
		return(1);
	    }
	};
	$result = $post->as_XML;
	local $SIG{__WARN__};

        $post->delete();
        $result =~ s/<[^>]+>//g;
        while (        $result =~ s/\n\n\n/\n\n/gm ) { 1; }

        last;
    }

    $xhtml->delete;
    return($result);

}

sub daily_links_page {
    ( my $xhtml ) = ( @_ );

    # Daily Links use a difficult 1990-style structure instead of CSS :(
    # they're very hard to parse
    my $result = '';
    if ($opt{'g'}) {
        $xhtml = &links2gemini($xhtml);
    }

    my $tmp =  HTML::Element->new('~literal');
    $tmp->push_content("\n");
    for my $toc ($xhtml->findnodes('//div[contains(@id,"contents")]')) {
	$toc->replace_with($tmp);
    }

    for my $post ($xhtml->findnodes('//div[@class="post"]')){

        for my $pp ($post->findnodes('./p[@class="gemini"]')) {
	    # delete blurb promoting gemini
	    $pp->delete;
	}

	for my $pp ($post->findnodes('./p[@class="meta"]')) {
            # keep the text but ditch the hyperlinks
            my $text = $pp->as_text;
            $text =~ s/\s+/ /gm;
            $text =~ s/\n+/ /gm;

            my $tmp =  HTML::Element->new('p');
            $tmp->push_content($text);
            $pp->replace_with($tmp);

        }

        for my $h1 ($post->findnodes('./h1')) {
            # page date from H1
            my $n = $h1->as_text.qq(\n\n);
            my $tmp = HTML::Element->new('~literal');
            $tmp->push_content("\n#\t● $n");
            $h1->replace_with($tmp);

            last;
        }

        for my $h2 ($post->findnodes('./h2')) {
            # page title from H2
            my $n = $h2->as_text.qq(\n\n);
            my $tmp = HTML::Element->new('~literal');
            $tmp->push_content("\n#\t● $n");
            $h2->replace_with($tmp);

            last;
        }

        for my $ul ($post->findnodes('./div/ul')) {
            # deal with the nested, unorderedd lists by flattening them
            $ul = &ul(0, $ul);
        }

        $result = $post->as_text;
        $post->delete();
        while ( $result =~ s/\n\n\n/\n\n/gm ) { 1; }

        last;
    }
    return($result);
}

sub ul {
    my ($depth, $ul) = ( @_ );
    $depth++;

    for my $h3 ($ul->findnodes('./li/h3')) {
        my $new_h3 = HTML::Element->new('~literal');
        $new_h3->push_content( "\n##\t".$h3->as_text."\n\n" );
        $h3->replace_with($new_h3);
    }

    for my $u ($ul->findnodes('./li/ul')) {
        $u = &ul($depth, $u);
    }

    my $new_li = HTML::Element->new('~literal');
    for my $l ($ul->findnodes('./li[not(.//ul)]')) {
        for my $h5 ($l->findnodes('./h5')) {
            my $new_h5 = HTML::Element->new('~literal');
            my $href ='';
	    my $title = $h5->as_text;
            for my $a ($h5->findnodes('./a')) {
                $href = $a->attr('href');
                $new_li->push_content("\n=>\t".$href."\t↺ ".$title."\n\n");
            }
        }
        for my $q ($l->findnodes('./blockquote')) {
            my $new_q = HTML::Element->new('~literal');
            for my $p ($q->findnodes('./p')) {
                $new_li->push_content( "*\t".$p->as_text."\n\n" );
            }
        }
        $l->replace_with($new_li);
    }
    return($ul);
}

sub links2gemini {
    my ($xhtml) = (@_);
    # convert TR http and https links to gemini

    for my $anchor ($xhtml->findnodes('//a[@href]')) {
        my $href = $anchor->attr('href');

        if ($href!~m/^http/) {
	    next;
	} elsif ($href=~m/\.webm$/) {
	    next;
	}

	$href =~ s{/wiki/index\.php/} {/wiki/};

        my $url  = URI->new($href);
        # not all schemes support 'host' objects, thus the sequence matters
        if (($url->scheme eq 'http' || $url->scheme eq 'https')
            && $url->host =~ m/techrights\.org$/ ) {
            $url->host('gemini.techrights.org');
            $url->scheme('gemini');
	    $anchor->attr('href' => $url->canonical);
	}
    }
    return ($xhtml);
}

Gemini/Gemini-Proxy/gemini-proxy.pl

#!/usr/bin/perl -T

# 2202-02-04

# shell equivalent:
# echo -ne "gemini://gemini.techrights.org:1965/\r\n\r\n"  \
# | openssl s_client -connect "gemini.techrights.org:1965" \
#     -servername "gemini.techrights.org" -quiet -ign_eof

use 5.32.1;                           # state() requires 5.010 or later
				     # break requires 5.1.0 or later
use IO::Socket::SSL;                 # libio-socket-ssl-perl
# use HTML::Entities;                  # libhtml-parser-perl

use CGI::Fast;                       # libcgi-fast-perl
use CGI qw( :standard );
# use CGI::Carp qw( fatalsToBrowser ); # Remove for production use

use strict;
use warnings;

use English qw( -no_match_vars );

$CGI::POST_MAX = 1024 * 1024 * 10;  # max 10MB posts

# binmode *STDOUT, ':utf8';
# binmode *STDIN,  ':utf8';

my $socket_name = "/run/nginx/nginx.gemini.proxy.0.sock";
my $socket_path = "/run/nginx/";

# destroy any pre-existing socket
-S $socket_name && unlink $socket_name;

umask 007;
$ENV{FCGI_SOCKET_PATH}  = $socket_name;
$ENV{FCGI_LISTEN_QUEUE} = 50;

my $url;

while (my $query = CGI::Fast->new) {
    print qq(Content-type: text/html\n\n);
    &print_head;
    print qq(\n);

    # for my $e (sort keys %ENV) {
    #    print qq($e : $ENV{$e} 
\n); # } # for my $p ($query->param) { # print qq($p ),$query->param($p),qq(
\n); # } my $action = $query->param('action') || 0; my $url = $query->param('url') || 0; $url = &sanitize($url); if ($url) { my ($status, @p) = ('',''); my $count = 3; while ($count--) { ($status, @p ) = &fetch_gemini($url); my $meta; ($status, $meta) = ($status =~ m/^([0-9]{2})\s+(.*)$/); if ($status =~ /^3/) { $url = &sanitize($meta); next; } else { last; } } my $proxy = "/proxy"; print qq(

); #override alignment print qq([Status code $status | ),&status($status), qq(]

\n); my $http = $url; $http =~ s|/index.gmi$|/|; $http =~ s|(?the original in the official Web site 🢡
\n); print qq(It may also have images and/or videos.

\n); print qq(

); } print qq(Note: this is only a proxy (Gemini→HTTP) gateway. ); print qq(We highly recommend using proper ); print qq(Gemini client software ); print qq(instead.

\n); &gemtext_to_xhtml($proxy, $url, @p); } else { &print_form; } &print_tail; } exit(0); sub fetch_gemini { my ($url) = (@_); # simple client my $cl = IO::Socket::SSL->new( PeerHost=>"gemini.techrights.org", PeerPort=>1965, Timeout=>2, Proto => 'tcp', # SSL_ca_path => '/home/pi/Certs/', SSL_ca_file => '/home/gemini/bin/Gemini-Proxy/Cert-Cache/gemini.tecrights.org.pem', SSL_hostname => "gemini.techrights.org", SSL_verify_mode => SSL_VERIFY_PEER, # overriden by next line ... # SSL_verify_mode => SSL_VERIFY_NONE, # unsafe kludge ) or die("Failed to connect: $!, '$SSL_ERROR'\n"); print $cl $url,"\r\n\r\n"; my @page = (); my $status = <$cl>; my $tmp = <$cl>; while (my $line = <$cl> ){ push(@page,$line); } close($cl); return($status, @page); } sub gemtext_to_xhtml { my ($proxy, $url, @page) = (@_); my ($path ) = ( $url =~ m{^.*//[^/]+(.*)$} ); print qq(
\n); while (my $line = shift @page) { chomp($line); if (! $line) { # $line ="\x{a0}"; # non-breaking space $line =" "; # non-breaking space } else { $line =~ s/\&/\&/gm; $line =~ s/$line\n); } elsif ($line =~ m/^##\s/) { print qq(

$line

\n); } elsif ($line =~ m/^###\s/) { print qq(

$line

\n); } elsif ($line =~ m/^=>\s/) { my ($link, $text) = ("", ""); if (($link, $text ) = ($line =~ m/^=>\s+(\S+)\s+(.*)$/)) { 1; } elsif (($link) = ($line =~ m/^=>\s+(\S+)/)) { 1; } else { 0; } if ($link =~ m{^/} ) { $link = "gemini://gemini.techrights.org/$link"; } elsif( $link !~ m{^\w+\://?} ) { $link = "gemini://gemini.techrights.org/$path$link"; } if( ! defined($text) ) { $text = $link; } $link =~ s|(?$text

\n); } else { print qq(

$text

\n); } } elsif ($line =~ m/^>\s/) { $line =~ s/^>\s+//; print qq(
$line
\n); } elsif ($line =~ m/^\*\s/) { $line =~ s/^\*\s+//; print qq(

● $line

\n); } elsif ($line =~ m/^\`\`\`/) { print qq(
\n);
	    while (my $l = shift @page) {
		if ($l =~ m/^\`\`\`/) {
		    last;
		}
		$l =~ s/\n);
	} else {
	    print qq(

$line

\n); } } print qq(
\n); return(1); } sub print_head { print qq(\n); print qq(\n); print qq(\n); print qq( \n); print qq( Gemini Proxy for Techrights\n); print qq( \n); print qq(\n); return(1); } sub print_tail { print qq( \n\n); return(1); } sub print_form { print qq(
\n); print qq(Gemini URI: \n); print qq(  \n); print qq(
 
\n); print qq(
\n); return(1); } sub status { my ($status) =(@_); $status = int($status); state %code = ( 10 => 'INPUT', 11 => 'SENSITIVE INPUT', 20 => 'SUCCESS', 30 => 'REDIRECT - TEMPORARY', 31 => 'REDIRECT - PERMANENT', 40 => 'TEMPORARY FAILURE', 41 => 'SERVER UNAVAILABLE', 42 => 'CGI ERROR', 43 => 'PROXY ERROR', 44 => 'SLOW DOWN', 50 => 'PERMANENT FAILURE', 51 => 'NOT FOUND', 52 => 'GONE', 53 => 'PROXY REQUEST REFUSED', 59 => 'BAD REQUEST', 60 => 'CLIENT CERTIFICATE REQUIRED', 61 => 'CERTIFICATE NOT AUTHORISED', 62 => 'CERTIFICATE NOT VALID', ); return(exists($code{$status}) ? $code{$status} : 'UNKNOWN' ); } sub sanitize { my ($url) = (@_); $url =~ s/\s+//gm; $url =~ s|/index.gmi$|/|; $url =~ s|(?

Gemini/Gemini-Proxy/robots.txt

User-agent: *
Disallow: /proxy

Gemini/Gemini-Proxy/gemini-proxy

##
# You should look at the following URL's in order to grasp a solid understanding
# of Nginx configuration files in order to fully unleash the power of Nginx.
# https://www.nginx.com/resources/wiki/start/
# https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/
# https://wiki.debian.org/Nginx/DirectoryStructure
#
# In most cases, administrators will remove this file from sites-enabled/ and
# leave it as reference inside of sites-available where it will continue to be
# updated by the nginx packaging team.
#
# This file will automatically load configuration files provided by other
# applications, such as Drupal or Wordpress. These applications will be made
# available underneath a path with that package name, such as /drupal8.
#
# Please see /usr/share/doc/nginx-doc/examples/ for more detailed examples.
##

# Default server configuration
#
server {
	listen 80;
#	listen [::]:80 default_server;

	root /var/www/html;

	# Add index.php to the list if you are using PHP
	index index.html index.htm index.nginx-debian.html;

	server_name _;

	location / {
		# First attempt to serve request as file, then
		# as directory, then fall back to displaying a 404.
		try_files $uri $uri/ =404;
	}

        location /proxy {
                fastcgi_pass unix:/var/run/nginx/nginx.gemini.proxy.0.sock;
                fastcgi_param SCRIPT_FILENAME /usr/local/bin/gemini-proxy.pl;
                fastcgi_param QUERY_STRING    $query_string;
                fastcgi_param REQUEST_METHOD  $request_method;
                fastcgi_param CONTENT_TYPE    $content_type;
                fastcgi_param CONTENT_LENGTH  $content_length;
                include fastcgi_params;

                deny 20.34.0.0/15;      # bing 病
                deny 20.36.0.0/14;      # bing 病
                deny 20.40.0.0/13;      # bing 病
                deny 20.48.0.0/12;      # bing 病
                deny 20.64.0.0/10;      # bing 病
                deny 20.128.0.0/16;     # bing 病
                deny 23.96.0.0/13;      # bing 病
                deny 34.64.0.0/10;      # googlebot
                deny 40.74.0.0/15;      # bing 病
                deny 40.76.0.0/14;      # bing 病
                deny 40.80.0.0/12;      # bing 病
                deny 40.96.0.0/12;      # bing 病
                deny 40.124.0.0/16;     # bing 病
                deny 40.112.0.0/13;     # bing 病
                deny 40.120.0.0/14;     # bing 病
                deny 40.125.0.0/17;     # bing 病
		deny 52.84.0.0/14;      # bing 病
		deny 52.88.0.0/13;      # bing 病
                deny 52.136.0.0/13;     # ddg / bing 病
                deny 52.132.0.0/14;     # ddg / bing 病
		deny 52.145.0.0/16;     # bing 病
		deny 52.146.0.0/15;     # bing 病
		deny 52.148.0.0/14;     # bing 病
		deny 52.152.0.0/13;     # bing 病
		deny 52.160.0.0/11;     # bing 病
                deny 157.56.0.0/14;     # bing 病
                deny 157.54.0.0/15;     # bing 病
                deny 157.60.0.0/16;     # bing 病
                deny 207.46.0.0/16;     # bing 病
                deny 37.252.64.0/19;
                deny 66.249.64.0/19;    # google bot
                deny 116.128.0.0/10;    # baidu
                deny 162.142.125.0/24;  # censys
		deny 185.250.240.0/24;
		deny 208.80.192.0/21;
                deny 77.88.0.0/18;      # yandex
                deny 93.158.128.0/18;   # yandex
        }

        location = / {
            return 301 http://gemini.techrights.org/proxy?url=gemini://gemini.techrights.org/;
        }

}


server {
        listen 443 ssl;
#        listen [::]:443 ssl;
        include snippets/self-signed.conf;
        include snippets/ssl-params.conf;

        root /var/www/html;

        # Add index.php to the list if you are using PHP
        index index.html index.htm index.nginx-debian.html;

        server_name _;

        location / {
                # First attempt to serve request as file, then
                # as directory, then fall back to displaying a 404.
                try_files $uri $uri/ =404;
        }

        location /proxy {
                fastcgi_pass unix:/var/run/nginx/nginx.gemini.proxy.0.sock;
                fastcgi_param SCRIPT_FILENAME /usr/local/bin/gemini-proxy.pl;
                fastcgi_param QUERY_STRING    $query_string;
                fastcgi_param REQUEST_METHOD  $request_method;
                fastcgi_param CONTENT_TYPE    $content_type;
                fastcgi_param CONTENT_LENGTH  $content_length;
                include fastcgi_params;
        }

	location = / {
	    return 301 https://gemini.techrights.org/proxy?url=gemini://gemini.techrights.org/;
	}

}


# Virtual Host configuration for example.com
#
# You can move that to a different file under sites-available/ and symlink that
# to sites-enabled/ to enable it.
#
#server {
#	listen 80;
#	listen [::]:80;
#
#	server_name example.com;
#
#	root /var/www/example.com;
#	index index.html;
#
#	location / {
#		try_files $uri $uri/ =404;
#	}
#}

Gemini/Gemini-Proxy/Cert-Cache/.directory-listing-ok

Gemini/Gemini-Proxy/Cert-Cache/gemini.tecrights.org.pem

-----BEGIN CERTIFICATE-----
MIIBVTCB+6ADAgECAgEqMAoGCCqGSM49BAMCMCAxHjAcBgNVBAMMFWdlbWluaS50
ZWNocmlnaHRzLm9yZzAgFw03NTAxMDEwMDAwMDBaGA80MDk2MDEwMTAwMDAwMFow
IDEeMBwGA1UEAwwVZ2VtaW5pLnRlY2hyaWdodHMub3JnMFkwEwYHKoZIzj0CAQYI
KoZIzj0DAQcDQgAEI0o5Lnz2dDrRtj/G7fbTQQlIij6iJxkw6B6pFd19Bk+PoPxy
sVjC81ORc8h14HbDD8NBDWIL0SdPtzcYNT/A+aMkMCIwIAYDVR0RBBkwF4IVZ2Vt
aW5pLnRlY2hyaWdodHMub3JnMAoGCCqGSM49BAMCA0kAMEYCIQDCs7jcvilZocLT
lsvT9algZT2z6s1UHJ2VPffKIh2B2wIhAOuiE2nhxYijTimG+YM4xMH4bNIKA6Be
wJ8hAedbybAt
-----END CERTIFICATE-----

Gemini/Gemini-Proxy/.directory-listing-ok

Gemini/Gemini-Proxy/gemini-proxy.service

[Unit]
Description=Gemini Proxy Service
After=network.target
After=nginx.target

[Service]
Type=simple
User=gemprox
PermissionsStartOnly=true
ExecStartPre=-/bin/mkdir -p /run/nginx/
ExecStartPre=/bin/chmod g=rwxs /run/nginx/
ExecStartPre=/bin/chown www-data:gemprox /run/nginx/
ExecStart=/usr/local/bin/gemini-proxy.pl
# or always, on-abort, etc
Restart=on-failure
Nice=5

[Install]
WantedBy=multi-user.target

Gemini/tr-update-planet.sh

#!/bin/bash

# Initial 2022-03-06
# Last updated 2022-03-18

set -v -x
# PATH=/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin

# Get key dates first

daybeforeyesterday=$(date --date "2 days ago" +"%Y-%m-%d")
yesterday=$(date --date yesterday +"%Y-%m-%d")
today=$(date --date today +"%Y-%m-%d")

##################################
# Start main page of Planet

echo "# Planet Gemini" > ~/gemini/planet/index.gmi

echo "Welcome to Planet Gemini. This is where we aggregate sources to help us identify new material in gopher:// and gemini:// space." >> ~/gemini/planet/index.gmi
echo "## Get added" >> ~/gemini/planet/index.gmi
echo "Contact us if you want yours to be added too." >> ~/gemini/planet/index.gmi

echo "=>  /about Contacting us" >> ~/gemini/planet/index.gmi
echo "=>  / Go back to Techrights (Main Index)" >> ~/gemini/planet/index.gmi

echo "# New/Updated So Far Today and Yesterday" >> ~/gemini/planet/index.gmi

echo "=> /planet/latest Latest Items"  >> ~/gemini/planet/index.gmi

##################################
# Update the page with latest posts in the Planet (to use prior snapshot, not upcoming)

echo "# Latest Posts/Articles Across Geminispace"  > ~/gemini/planet/latest/index.gmi
echo "This page aggregates posts found for the current day and prior day. It's typically updated every 3 hours."  >> ~/gemini/planet/latest/index.gmi


cat  ~/gemini/planet/othercapsules/${today}.gmi  >> ~/gemini/planet/latest/index.gmi

cat  ~/gemini/planet/othercapsules/${yesterday}.gmi  >> ~/gemini/planet/latest/index.gmi

echo "____________________________________________________"  >> ~/gemini/planet/latest/index.gmi
echo "=> / Go back to Techrights (Main Index)"  >> ~/gemini/planet/latest/index.gmi
echo "=> /planet Planet (Full)"  >> ~/gemini/planet/latest/index.gmi
echo -n "Last update cycle ended "  >> ~/gemini/planet/latest/index.gmi
date --date today "+%A, %B %d, %Y at %H:%M"  >> ~/gemini/planet/latest/index.gmi

##################################
# Now go back to main Planet page

echo "## More Dates" >> ~/gemini/planet/index.gmi

echo "=> /planet/othercapsules Older Dates (Archive)" >> ~/gemini/planet/index.gmi
echo "=> /planet/othercapsules/allinone.gmi All in one, past 3 days combined, fused and compacted"  >> ~/gemini/planet/index.gmi
echo "=> /planet/othercapsules/new.gmi New (added since last update cycle)"  >> ~/gemini/planet/index.gmi

echo "## Updates" >> ~/gemini/planet/index.gmi

echo -n "Last update cycle started " >> ~/gemini/planet/index.gmi
date --date today "+%A, %B %d, %Y at %H:%M" >> ~/gemini/planet/index.gmi
echo  "Page updated every 3 hours ⦼" >> ~/gemini/planet/index.gmi

echo "## Sources" >> ~/gemini/planet/index.gmi

echo "Some suggested feeds (for syndication) are listed below." >> ~/gemini/planet/index.gmi
echo "=> /git/tr-git/Gemini/gemini-feeds.gmi List of feeds" >> ~/gemini/planet/index.gmi
echo "=> /git/tr-git/Gemini/gemini-capcom.gmi More active feeds" >> ~/gemini/planet/index.gmi

echo "# Full Lists (Item Listing, Undated)" >> ~/gemini/planet/index.gmi
echo "Pertinent capsules followed by list of lists, credit added where it is due" >> ~/gemini/planet/index.gmi

# Note: dirty hacks below, might be worth tidying up in the future
# echo -ne "gemini://gemini.techrights.org/feed.xml\r\n\r\n" | openssl s_client -servername gemini.techrights.org -quiet -ign_eof -connect gemini.techrights.org:1965 | sed -e '1{/^[0-9][0-9]/d}' | xmlstarlet sel -E utf-8 -T -t -m '//item' -o '=> ' -v './link' -o "  " -v './title' -n >> ~/gemini/planet/index.gmi

echo "## mieum" >> ~/gemini/planet/index.gmi
(echo "gemini://rawtext.club/~mieum/poems/atom.xml"; sleep 1) \
	| openssl s_client -connect rawtext.club:1965 \
	| grep -e title -e link \
	| tac \
	| sed "s/    //" \
	| sed "s/<\/title>//" \
	| sed -e :a -e N -e '$!ba' -e 's/gmi\n/gmi /g' \
	| grep "=>" >> ~/gemini/planet/index.gmi

echo "##  🅰🆅🅰🅻🅾🆂.🅼🅴" >> ~/gemini/planet/index.gmi
echo -ne "gemini://avalos.me/gemlog/atom.xml\r\n\r\n" \
	| openssl s_client -servername avalos.me -quiet -ign_eof -connect avalos.me:1965 \
	| grep -e title -e link \
	| tac \
	| sed "s//=> /" \
	| cut --d="\"" -f2-2 \
	| sed -e "s///" -e "s/<\/title>//" -e "s/<\/link>//" \
	| sed "s/gemini:/=> gemini:/" \
	| sed -e :a -e N -e '$!ba' -e 's/gmi\n/gmi /g' \
	| grep "=>" \
	| grep ".gmi"  >> ~/gemini/planet/index.gmi

echo "## 🆂🅸🆇🅾🅷🆃🅷🆁🅴🅴.🅲🅾🅼" >> ~/gemini/planet/index.gmi
echo -ne "gemini://sixohthree.com:1965/atom.xml\r\n\r\n" \
	| openssl s_client -servername sixohthree.com -quiet -ign_eof -connect sixohthree.com:1965 \
	| grep -e title -e link \
	| tac \
	| sed "s/<link>/=> /" \
	| cut --d="\"" -f4-4 \
	| sed -e "s/<title>//" -e "s/<\/title>//" -e "s/<\/link>//" \
	| sed "s/gemini:/=> gemini:/" \
	| sed -e :a -e N -e '$!ba' -e 's/gmi\n/gmi /g' \
	| grep "=>" \
	| grep ".gmi"  >> ~/gemini/planet/index.gmi

echo "## 🆅🅺3.🆆🆃🅵 " >> ~/gemini/planet/index.gmi
echo -ne "gemini://vk3.wtf:1965/gemlog/atom.xml\r\n\r\n" \
	| openssl s_client -servername vk3.wtf -quiet -ign_eof -connect vk3.wtf:1965 \
	| grep -e title -e link \
	| tac \
	| sed "s/<link>/=> /" \
	| cut --d="\"" -f2-2 \
	| sed -e "s/<title>//" -e "s/<\/title>//" -e "s/<\/link>//" \
	| sed "s/gemini:/=> gemini:/" \
	| sed -e :a -e N -e '$!ba' -e 's/gmi\n/gmi /g' \
	| grep "=>"  \
	| grep ".gmi"  >> ~/gemini/planet/index.gmi

echo "## 🆃🅷🅴🅻🅰🅼🅱🅳🅰🅻🅰🅱.🆇🆈🆉 " >> ~/gemini/planet/index.gmi
echo -ne "gemini://thelambdalab.xyz:1965/atom.xml\r\n\r\n" \
	| openssl s_client -servername thelambdalab.xyz -quiet -ign_eof -connect thelambdalab.xyz:1965 \
	| grep -e title -e link \
	| tac \
	| sed "s/<link>/=> /" \
	| cut --d="\"" -f2-2 \
	| sed -e "s/<title>//" -e "s/<\/title>//" -e "s/<\/link>//" \
	| sed "s/gemini:/=> gemini:/" \
	| sed -e :a -e N -e '$!ba' -e 's/gmi\n/gmi /g' \
	| grep "=>"  \
	| grep ".txt"  >> ~/gemini/planet/index.gmi

# OFFLINE JULY 2022:
# echo "## 🅳🆁🅴🆆🅳🅴🆅🅰🆄🅻🆃.🅲🅾🅼" >> ~/gemini/planet/index.gmi
# echo -ne "gemini://drewdevault.com/feed.xml\r\n\r\n" \
# 	| openssl s_client -servername drewdevault.com -quiet -ign_eof -connect drewdevault.com:1965 \
# 	| grep -e title -e link \
# 	| tac \
# 	| sed "s/<link>/=> /" \
# 	| cut --d="\"" -f2-2 \
# 	| sed -e "s/<title>//" -e "s/<\/title>//" -e "s/<\/link>//" \
# 	| sed "s/      //" \
# 	| sed -e :a -e N -e '$!ba' -e 's/gmi\n/gmi /g' \
# 	| grep "=>"  \
# 	| grep ".gmi"  >> ~/gemini/planet/index.gmi

echo "## Tux Machines">> ~/gemini/planet/index.gmi
echo -ne "gemini://gemini.tuxmachines.org/feed.xml\r\n\r\n" \
    | openssl s_client -servername gemini.tuxmachines.org -quiet -ign_eof -connect gemini.tuxmachines.org:1965 \
    | grep -e title -e link \
    | sed "s/<link>/=> /" \
    | cut --d="\"" -f4-4 \
    | sed -e "s/<title>//" -e "s/<\/title>//" -e "s/<\/link>//" \
    | sed "s/gemini:/=> gemini:/" \
    | sed -e :a -e N -e '$!ba' -e 's/gmi\n/gmi /g' \
    | grep "=>" \
    | grep ".gmi" >> ~/gemini/planet/index.gmi

echo "## 🅼🅾🅳🅳🅴🅳🅱🅴🅰🆁.🆇🆈🆉">> ~/gemini/planet/index.gmi
echo -ne "gemini://moddedbear.xyz/logs/atom.xml\r\n\r\n" \
	| openssl s_client -servername moddedbear.xyz -quiet -ign_eof -connect moddedbear.xyz:1965 \
	| grep -e title -e link \
	| tac \
	| sed "s/<link>/=> /" \
	| cut --d="\"" -f2-2 \
	| sed -e "s/   <title>//" -e "s/<\/title>//; s/<\/link>//" \
	| sed "s/      //" \
	| sed "s/gemini:/=> gemini:/" \
	| sed -e :a -e N -e '$!ba' -e 's/gmi\n/gmi /g' \
	| grep "=>"  \
	| grep ".gmi"  >> ~/gemini/planet/index.gmi

# echo "## nader.pm">> ~/gemini/planet/index.gmi
# echo -ne "gemini://nader.pm/atom.xml\r\n\r\n" | openssl s_client -servername nader.pm -quiet -ign_eof -connect nader.pm:1965 | grep -e title -e link | tac | sed "s/<link>/=> /" | cut --d="\"" -f2-2 | sed -e "s/<title>//" -e "s/<\/title>//" -e "s/<\/link>//" |  sed "s/gemini:/=> gemini:/" | sed -e :a -e N -e '$!ba' -e 's/gmi\n/gmi /g' | grep "=>" | grep ".gmi" >> ~/gemini/planet/index.gmi

echo "## 🅶🅴🅼🅸🅽🅸.🅸🅾🆂🅰.🅸🆃">> ~/gemini/planet/index.gmi
echo -ne "gemini://gemini.iosa.it/gemlog/atom.xml\r\n\r\n" \
	| openssl s_client -servername gemini.iosa.it -quiet -ign_eof -connect gemini.iosa.it:1965 \
	| grep -e title -e link \
	| tac \
	| sed "s/<link>/=> /" \
	| cut --d="\"" -f2-2 \
	| sed -e "s/<title>//" -e "s/<\/title>//" -e "s/<\/link>//" \
	| sed "s/gemini:/=> gemini:/" \
	| sed -e :a -e N -e '$!ba' -e 's/gmi\n/gmi /g' \
	| grep "=>"  \
	| grep ".gmi" >> ~/gemini/planet/index.gmi

echo "## 🅲🅰🅻🅲🆄🅾🅳🅴.🅲🅾🅼">> ~/gemini/planet/index.gmi
echo -ne "gemini://calcuode.com/gemlog/atom.xml\r\n\r\n" \
	| openssl s_client -servername calcuode.com -quiet -ign_eof -connect calcuode.com:1965 \
	| grep -e title -e link \
	| tac \
	| sed "s/<link>/=> /" \
	| cut --d="\"" -f2-2 \
	| sed -e "s/<title>//" -e "s/<\/title>//" -e "s/<\/link>//" \
	| sed "s/gemini:/=> gemini:/" \
	| sed -e :a -e N -e '$!ba' -e 's/gmi\n/gmi /g' \
	| grep "=>"  \
	| grep ".gmi" >> ~/gemini/planet/index.gmi

### Inactive May 2022, hence commented out
###     echo "## jfh.me">> ~/gemini/planet/index.gmi
###     echo -ne "gemini://jfh.me/atom.xml\r\n\r\n" \
###         | openssl s_client -servername jfh.me -quiet -ign_eof -connect jfh.me:1965 \
###         | grep -e title -e link \
###         | tac \
###         | sed "s/<link>/=> /" \
###         | cut --d="\"" -f2-2 \
###         | sed -e "s/<title>//" -e "s/<\/title>//" -e "s/<\/link>//" \
###         |  sed "s/gemini:/=> gemini:/" \
###         | sed -e :a -e N -e '$!ba' -e 's/gmi\n/gmi /g' \
###         | grep "=>"  \
###         | grep ".gmi" >> ~/gemini/planet/index.gmi

echo "## 🅶🅰🅻🅰🆇🆈🅷🆄🅱.🆄🅺">> ~/gemini/planet/index.gmi
echo -ne "gemini://galaxyhub.uk/articles/atom.xml\r\n\r\n" \
	| openssl s_client -servername galaxyhub.uk -quiet -ign_eof -connect galaxyhub.uk:1965 \
	| grep -e title -e link \
	| tac \
	| sed "s/<link>/=> /" \
	| cut --d="\"" -f2-2 \
	| sed -e "s/<title>//" -e "s/<\/title>//" -e "s/<\/link>//" \
	| sed "s/gemini:/=> gemini:/" \
	| sed -e :a -e N -e '$!ba' -e 's/gmi\n/gmi /g' \
	| grep "=>"  \
	| grep ".gmi" >> ~/gemini/planet/index.gmi

# echo "## gemini://cosmic.voyage">> ~/gemini/planet/index.gmi
# echo -ne "gemini://cosmic.voyage/atom.xml\r\n\r\n" | openssl s_client -servername cosmic.voyage -quiet -ign_eof -connect cosmic.voyage:1965 | grep -e title -e link | tac | sed "s/<link>/=> /" | cut --d="\"" -f2-2 | sed -e "s/<title>//" -e "s/<\/title>//" -e "s/<\/link>//" |  sed "s/gemini:/=> gemini:/" | sed -e :a -e N -e '$!ba' -e 's/gmi\n/gmi /g' | grep "=>"  | grep ".gmi" >> ~/gemini/planet/index.gmi

echo "## 🅶🅴🅼🅸🅽🅸.🆄🅲🅰🅽🆃.🅾🆁🅶">> ~/gemini/planet/index.gmi
echo -ne "gemini://gemini.ucant.org/gemlog/atom.xml\r\n\r\n" \
	| openssl s_client -servername gemini.ucant.org -quiet -ign_eof -connect gemini.ucant.org:1965 \
	| grep -e title -e link \
	| tac \
	| sed "s/<link>/=> /" \
	| cut --d="\"" -f2-2 \
	| sed -e "s/<title>//" -e "s/<\/title>//" -e "s/<\/link>//" \
	| sed "s/gemini:/=> gemini:/" \
	| sed -e :a -e N -e '$!ba' -e 's/gmi\n/gmi /g' \
	| grep "=>"  \
	| grep ".gmi" >> ~/gemini/planet/index.gmi

echo "## 🅻🅾🆃🆃🅰🅻🅸🅽🆄🆇🅻🅸🅽🅺🆂.🅲🅾🅼">> ~/gemini/planet/index.gmi
echo -ne "gemini://gemini.lottalinuxlinks.com/posts/feed.xml\r\n\r\n" \
	| openssl s_client -servername gemini.lottalinuxlinks.com -quiet -ign_eof -connect gemini.lottalinuxlinks.com:1965 \
	| grep -e title -e link \
	| tac \
	| sed "s/<link>/=> /" \
	| cut --d="\"" -f2-2 \
	| sed -e "s/<title>//" -e "s/<\/title>//" -e "s/<\/link>//" \
	| sed "s/gemini:/=> gemini:/" \
	| sed -e :a -e N -e '$!ba' -e 's/gmi\n/gmi /g' \
	| grep "=>" \
	| grep -v "id>" \
	| grep ".gmi" >> ~/gemini/planet/index.gmi

echo "## 🅰🅿.🆂🆆🅰🅽.🆀🆄🅴🆂🆃">> ~/gemini/planet/index.gmi
echo -ne "gemini://cap.swan.quest/p/atom.xml\r\n\r\n" \
	| openssl s_client -servername cap.swan.quest -quiet -ign_eof -connect cap.swan.quest:1965 \
	| grep -e title -e link \
	| tac \
	| sed "s/<link>/=> /" \
	| cut --d="\"" -f2-2 \
	| sed -e "s/<title>//" -e "s/<\/title>//" -e "s/<\/link>//" \
	| sed "s/gemini:/=> gemini:/" \
	| sed -e :a -e N -e '$!ba' -e 's/gmi\n/gmi /g' \
	| grep "=>"  \
	| grep ".gmi" >> ~/gemini/planet/index.gmi

echo "## 🅽🆈🆃🅿🆄.🅲🅾🅼" >> ~/gemini/planet/index.gmi
echo -ne "gemini://nytpu.com:1965/gemlog/atom.xml\r\n\r\n" \
	| openssl s_client -servername nytpu.com -quiet -ign_eof -connect nytpu.com:1965 \
	| grep -e title -e link \
	| tac \
	| sed "s/<link>/=> /" \
	| cut --d="'" -f4-4 \
	| sed -e "s/<title>//" -e "s/<\/title>//" -e "s/<\/link>//" \
	| sed "s/\/\//=> gemini:\/\//" \
	| sed -e :a -e N -e '$!ba' -e 's/gmi\n/gmi /g' \
	| grep "=>"  \
	| grep ".gmi" >> ~/gemini/planet/index.gmi

##################################
# Start imports section/s

echo "# Imported by nytpu.com">> ~/gemini/planet/index.gmi

echo "Credit due (original):" >> ~/gemini/planet/index.gmi
echo "=> gemini://nytpu.com/feed.gmi Alex" >> ~/gemini/planet/index.gmi

echo -ne "gemini://nytpu.com/feed.gmi\r\n\r\n" \
	| openssl s_client -servername nytpu.com -quiet -ign_eof -connect nytpu.com:1965 \
	| sed -n '/##/,$p' >> ~/gemini/planet/index.gmi

##################################
# Add/chain CAPCOM

echo "# Imported via CAPCOM">> ~/gemini/planet/index.gmi

echo "Credit due (original):" >> ~/gemini/planet/index.gmi
echo "=> gemini://gemini.circumlunar.space/capcom/ CAPCOM" >> ~/gemini/planet/index.gmi

echo -ne "gemini://gemini.circumlunar.space/capcom/\r\n\r\n" \
	| openssl s_client -servername gemini.circumlunar.space -quiet -ign_eof -connect gemini.circumlunar.space:1965 \
	| sed -n '/Aggregating 100/,$p' >> ~/gemini/planet/index.gmi

##################################
# Add/chain Spacewalk

echo "# Imported via Spacewalk">> ~/gemini/planet/index.gmi

echo "Credit due (original):" >> ~/gemini/planet/index.gmi
echo "=> gemini://rawtext.club/~sloum/spacewalk.gmi Spacewalk" >> ~/gemini/planet/index.gmi

# echo "\`\`\`" >> ~/gemini/planet/index.gmi
echo -ne "gemini://rawtext.club/~sloum/spacewalk.gmi\r\n\r\n" \
        | openssl s_client -servername rawtext.club -quiet -ign_eof -connect rawtext.club:1965 \
        | sed -n '/Oldest/,$p' >> ~/gemini/planet/index.gmi

##################################
# Add/chain Skyjake

echo "# Imported via Skyjake">> ~/gemini/planet/index.gmi

echo "Credit due (original):" >> ~/gemini/planet/index.gmi
echo "=> gemini://skyjake.fi/~Cosmos/ Skyjake" >> ~/gemini/planet/index.gmi

# openssl s_client -quiet -crlf   \
#	-servername skyjake.fi  \
#	-connect skyjake.fi:1965 \
#	| awk '{ print "response: " $0 }' gemini://skyjake.fi/~Cosmos/ |
# openssl s_client -quiet -crlf   \
#    -servername skyjake.fi   \
#    -connect skyjake.fi:1965 \
#|   awk '{ print "response: " $0 }'
#gemini://skyjake.fi/~Cosmos/ |  sed -n '/202/,$p' >> ~/gemini/planet/index.gmi

    # Temporary workaround below because the capsule acts weird
mv ~/Downloads/~Cosmos.gmi /tmp/cosmos
cat /tmp/cosmos  |  sed -n '/202/,$p' >> ~/gemini/planet/index.gmi

# sh ~/bin/gemini-get-cosmo.sh

##################################
# Add/chain Warmedal

echo "# Imported via Warmedal">> ~/gemini/planet/index.gmi

echo "Credit due (original):" >> ~/gemini/planet/index.gmi
echo "=> gemini://warmedal.se/~antenna/index.gmi Warmedal" >> ~/gemini/planet/index.gmi
echo "" >> ~/gemini/planet/index.gmi

echo -ne "gemini://warmedal.se/~antenna/index.gmi\r\n\r\n" \
        | openssl s_client -servername warmedal.se  -quiet -ign_eof -connect warmedal.se:1965 \
        | sed -n '/202/,$p' >> ~/gemini/planet/index.gmi

##################################
# Add/chain Calcuode

echo "# Imported via Calcuode">> ~/gemini/planet/index.gmi

echo "Credit due (original):" >> ~/gemini/planet/index.gmi
echo "=> gemini://calcuode.com/gmisub-aggregate.gmi Calcuode" >> ~/gemini/planet/index.gmi

echo -ne "gemini://calcuode.com/gmisub-aggregate.gmi\r\n\r\n" \
        | openssl s_client -servername calcuode.com -quiet -ign_eof -connect calcuode.com:1965 \
        | sed -n '/Entries/,$p' >> ~/gemini/planet/index.gmi

echo '________________________________________________'  >> ~/gemini/planet/index.gmi
echo -n 'Last updated '  >> ~/gemini/planet/index.gmi
date --date today "+%A, %B %d, %Y at %H:%M" >> ~/gemini/planet/index.gmi
echo "=>      /       Go back to Techrights (Main Index)" >> ~/gemini/planet/index.gmi

##################################
# Now update the archives
# We limits the scope of the scanner to imported sections

echo -n "## News for "  > ~/gemini/planet/othercapsules/${yesterday}.gmi
echo ${yesterday} >> ~/gemini/planet/othercapsules/${yesterday}.gmi
sed -n '/# Imported/,$p'  ~/gemini/planet/index.gmi \
    | sed -n -e "/${yesterday}/,/${daybeforeyesterday}/p" \
    | sort -u  -k2,2  \
    | grep -v " 2020-" \
    | grep -v " 2021-" \
    | sort -k3,3  \
    | grep "=>" >>  ~/gemini/planet/othercapsules/${yesterday}.gmi

    # Consider adding  sed 's/gopher:\/\//gopher:\/\/ [Gopher Link]/' or similar if many gopher links exist or need culling

echo -n "## News for "  > ~/gemini/planet/othercapsules/${today}.gmi
echo ${today} >> ~/gemini/planet/othercapsules/${today}.gmi
sed -n '/# Imported/,$p'  ~/gemini/planet/index.gmi \
    | sed -n -e "/${today}/,/${yesterday}/p" \
    | sort -u  -k2,2  \
    | grep -v " 2020-" \
    | grep -v " 2021-" \
    | sort -k3,3  \
    | grep "=>" >>  ~/gemini/planet/othercapsules/${today}.gmi

    # All in one, past 3 days combined, fused and compacted
cp ~/gemini/planet/othercapsules/allinone.gmi ~/gemini/planet/othercapsules/allinone-last.gmi
echo "# 3-day Outline for Geminispace" > ~/gemini/planet/othercapsules/allinone.gmi
cat  ~/gemini/planet/othercapsules/${today}.gmi \
    ~/gemini/planet/othercapsules/${yesterday}.gmi \
    ~/gemini/planet/othercapsules/${daybeforeyesterday}.gmi \
    | uniq \
    | grep -v "News for 20" \
    | sort -u  -k2,2 \
    | sort -r -k3,3 >> ~/gemini/planet/othercapsules/allinone.gmi

echo '____________‿︵‿︵ʚ˚̣̣̣͙ɞ・❉・ ʚ˚̣̣̣͙ɞ‿︵‿︵_________'  >> ~/gemini/planet/othercapsules/allinone.gmi
echo -n 'Last updated ' >>  ~/gemini/planet/othercapsules/allinone.gmi
date --date today "+%A, %B %d, %Y at %H:%M" >> ~/gemini/planet/othercapsules/allinone.gmi
echo "=> /planet/othercapsules/new.gmi Latest updates, sorted by time" >>  ~/gemini/planet/othercapsules/allinone.gmi
echo "=>      /       Go back to Techrights (Main Index)" >> ~/gemini/planet/othercapsules/allinone.gmi

    # Start the page afresh at first 3 hours of the day (assumes update cycles no greater than
    # 3 hours apart
case $(date +%H:%M) in
    (0[0]:*) echo "# New in Gemini (Since Last Time or Update Cycle)" > ~/gemini/planet/othercapsules/new.gmi;;
    (*)        echo "               ‿︵•‿︵•‿︵•‿︵" >> ~/gemini/planet/othercapsules/new.gmi;;
esac
    # Show differences in the direction of the "new" version, annul the "old" one aging away
diff ~/gemini/planet/othercapsules/allinone.gmi ~/gemini/planet/othercapsules/allinone-last.gmi  \
    | egrep '^[d<]' \
    | cut -c3-256 >> ~/gemini/planet/othercapsules/new.gmi

    # Start assembling the archives
echo "# Gemini Archive" > ~/gemini/planet/othercapsules/index.gmi

echo "Select any of the dates below. Those show all the Geminispace updates visible to us." >> ~/gemini/planet/othercapsules/index.gmi

    # Process starts of month specially
find /home/gemini/gemini/planet/othercapsules/ \
    -type f \
    -name "2*" \
    -exec basename {} \; \
    | sort  \
    | sed -r -e 's/([0-9]{4}-[0-9]{2}-[0-9]{2})\.gmi/\1.gmi \1/;   s/^/=> /' \
        -e '/01-01.*/i ### January ㋀' \
        -e '/02-01.*/i ### February ㋁' \
        -e '/03-01.*/i ### March ㋂' \
        -e '/04-01.*/i ### April ㋃' \
        -e '/05-01.*/i ### May ㋄' \
        -e '/06-01.*/i ### June ㋅' \
        -e '/07-01.*/i ### July ㋆' \
        -e '/08-01.*/i ### August ㋇' \
        -e '/09-01.*/i ### September ㋈' \
        -e '/10-01.*/i ### October ㋉' \
        -e '/11-01.*/i ### November ㋊' \
        -e '/12-01.*/i ### December ㋋' \
        >> ~/gemini/planet/othercapsules/index.gmi
echo -n 'Last updated ' >>  ~/gemini/planet/othercapsules/index.gmi
date --date today "+%A, %B %d, %Y at %H:%M" >> ~/gemini/planet/othercapsules/index.gmi
echo "=>      /       Go back to Techrights (Main Index)" >> ~/gemini/planet/othercapsules/index.gmi

    # Finish gracefully
exit 0

##################################
#			  Note/stale below
# -------
# echo -ne "gemini://gemini.jayeless.net/gemlog/atom.xml\r\n\r\n" \
	| openssl s_client -servername gemini.jayeless.net -quiet -ign_eof -connect gemini.jayeless.net:1965 \
	| grep -e title -e link \
	| tac \
	| sed "s/<link>/=> /" \
	| cut --d="\"" -f2-2 \
	| sed -e "s/<title>//" -e "s/<\/title>//" -e "s/<\/link>//" \
	|  sed "s/gemini:/=> gemini:/" \
	| sed -e :a -e N -e '$!ba' -e 's/gmi\n/gmi /g' \
	| grep "=>"  \
	| grep ".gmi"  >> ~/gemini/planet/index.gmi

# echo -ne "gemini://gemini.jayeless.net/gemlog/atom.xml\r\n\r\n" \
	| openssl s_client -servername gemini.jayeless.net -quiet -ign_eof -connect gemini.jayeless.net:1965 \
	| sed -e '1{/^[0-9][0-9]/d}' \
	| xmlstarlet sel -E utf-8 -T -t -m '//item' -o '=> ' -v './link' -o "  " -v './title' -n
# ------

# echo -ne "gemini://midnight.pub/feed.xml\r\n\r\n" \
	| openssl s_client -servername midnight.pub -quiet -ign_eof -connect midnight.pub:1965 \
	| grep -e title -e link \
	| tac \
	| sed "s/<link>/=> /" \
	| cut --d="\"" -f2-2 \
	| sed -e "s/<title>//" -e "s/<\/title>//" -e "s/<\/link>//" \
	|  sed "s/gemini:/=> gemini:/" \
	| sed -e :a -e N -e '$!ba' -e 's/gmi\n/gmi /g' \
	| grep "=>"  \
	| grep "posts"
# echo -ne "gemini://midnight.pub/feed.xml\r\n\r\n" \
	| openssl s_client -servername midnight.pub -quiet -ign_eof -connect midnight.pub:1965 \
	| sed -e '1{/^[0-9][0-9]/d}' \
	| xmlstarlet sel -E utf-8 -T -t -m '//item' -o '=> ' -v './link' -o "  " -v './title' -n
# ------
</pre><p><span class="gemini-fragment">=> </span><a href="/proxy/gemini.techrights.org/">Back to main index</a><br /><details>
<summary>Proxy Information</summary>
<dl>
<dt>Original URL</dt><dd><a href="gemini://gemini.techrights.org/git/tr-git/Gemini">gemini://gemini.techrights.org/git/tr-git/Gemini</a></dd>
<dt>Status Code</dt>
<dd>Success (20)</dd>
<dt>Meta</dt><dd><code>text/gemini;lang=en-GB</code></dd><dt>Capsule Response Time</dt>
<dd>397.843024 milliseconds</dd>
<dt>Gemini-to-HTML Time</dt>
<dd>7.571767 milliseconds</dd>
</dl>
<p>This content has been proxied by <a href="https://github.com/gemrest/september/tree/ba2dcfa">September (ba2dc)</a>.</p>
</details></body></html>