From bd274a20b46202f3a7d53a51a117cfd4110c3a67 Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Sat, 23 Jan 2021 17:18:10 -0700 Subject: new script: gmi2email Signed-off-by: Sean Whitton --- gmi2email | 272 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 272 insertions(+) create mode 100755 gmi2email (limited to 'gmi2email') diff --git a/gmi2email b/gmi2email new file mode 100755 index 0000000..c85728b --- /dev/null +++ b/gmi2email @@ -0,0 +1,272 @@ +#!/usr/bin/perl + +# gmi2email -- subscribe to gemlogs and read individual Gemini pages by e-mail + +# Copyright (C) 2021 Sean Whitton +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or (at +# your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +use 5.028; +use strict; +use warnings; + +use DBI; +use File::Spec::Functions "catfile"; +use IO::Socket::SSL; +use MIME::Lite; +use Config::Tiny; +use Text::Wrap; +use Try::Tiny; +use Getopt::Long; +use Email::Date::Format "email_date"; +use Time::Local; +use Mail::Field::AddrList; + +my ($from, $to, $do_subs, $inline_images); +GetOptions + "from=s" => \$from, + "to=s" => \$to, + "subscriptions!" => \$do_subs, + "inline-images!" => \$inline_images; + +my $conf_r = $ENV{XDG_CONFIG_HOME} || catfile $ENV{HOME}, ".config"; +my $conf_f = catfile $conf_r, "mailscripts", "gmi2email.config"; +-e $conf_f + or (defined $to and defined $from) + or die + "no config file or sufficient command line options: don't know who to mail"; +my $conf = Config::Tiny->new->read($conf_f); + +my %to_mail_opts = ( + from => ( + $from + or $conf->{_}->{from} + or die "no From: address set in config or on command line" + ), + to => ( + $to + or $conf->{_}->{to} + or die "no To: address set in config or on command line" + ), + inline_images => $inline_images // $conf->{_}->{inline_images} // 0 +); + +@ARGV or $do_subs or die "nothing to do\n"; + +for (@ARGV) { + my $data; + if (-f) { + open my $fh, "<", $_; + $data = [<$fh>]; + } else { + my $type; + ($type, $data) = gemini_fetch($_, abs_links => 1); + $type =~ m{^text/gemini} or die "$_ is not gemtext"; + } + gemtext_to_mail($data, %to_mail_opts)->send; +} + +exit unless $do_subs; + +my $subs_f = catfile $conf_r, "mailscripts", "gmi2email.subscriptions"; +-e $subs_f or die "no list of subscriptions"; +open my $subs_fh, "<", $subs_f; + +my $db_r = $ENV{XDG_CACHE_HOME} || catfile $ENV{HOME}, ".cache"; +my $db_d = catfile $db_r, "mailscripts"; +-d $db_d or mkdir $db_d; +my $db_f = catfile $db_d, "gmi2email.db"; +my $dbh = DBI->connect("dbi:SQLite:dbname=$db_f", "", ""); +$dbh->do("CREATE TABLE IF NOT EXISTS seen (uri TEXT PRIMARY KEY)") + or die "failed to initialise database"; + +foreach my $sub (<$subs_fh>) { + chomp $sub; + my ($gemlog, $type, $data); + #<<< + try { + ($type, $data) = gemini_fetch($sub, abs_links => 1); + } catch { + my ($code) = /"gemini error: ([1-6])/; + if ($code == 4) { + warn "temporary failure retrieving $sub"; + next; # try again next run + } else { + die "while retrieving $sub $_"; + } + }; + #>>> + $type =~ m{^text/gemini} or die "$sub is not gemtext"; + for (@$data) { + if (/^#\s*/ and not $gemlog) { + chomp($gemlog = $'); + } elsif (my ($uri, $y, $m, $d, $title) + = /^=>\s*(\S+)\s+([0-9]{4})-([0-9]{2})-([0-9]{2})[\s-]*(.*)/) { + my ($rows) + = $dbh->selectrow_array( + "SELECT COUNT(*) FROM seen WHERE uri = \"$uri\""); + if ($rows == 0) { + my $mail = 1; + my ($type, $data); + #<<< + try { + ($type, $data) = gemini_fetch($uri, abs_links => 1); + } catch { + warn "when fetching $uri, $_"; + my ($code) = /"gemini error: ([1-6])/; + if ($code == 4) { + next; # try again next run + } else { + $mail = 0; # don't try this one again + } + }; + #>>> + $dbh->do("INSERT INTO seen VALUES (\"$uri\")"); + $mail or next; + if ($type =~ m{^text/gemini}) { + gemtext_to_mail( + $data, %to_mail_opts, + gemlog => $gemlog // "unknown gemlog", + link_title => $title, + date => email_date timelocal 0, + 0, 12, $d, $m - 1, $y + )->send; + } else { + warn "$uri is not gemtext"; + } + } + } + } +} + +sub gemini_fetch { + my ($uri, %opts) = @_; + + # regexp from Alex Schroeder's moku-pona program + my ($scheme, $authority, $path, $query, $fragment) + = $uri + =~ m|(?:([^:/?#]+):)?(?://([^/?#]*))?([^?#]*)(?:\?([^#]*))?(?:#(.*))?|; + $scheme and $scheme eq "gemini" + or die "'$uri' does not use the gemini scheme"; + $authority or die "'$uri' lacks an authority"; + my ($host, $port) = split ":", $authority; + my $cl = IO::Socket::SSL->new( + PeerHost => $host, + PeerPort => $port // 1965, + SSL_verify_mode => SSL_VERIFY_NONE + ); + print $cl "$uri\r\n"; + + my ($status, $meta) = <$cl> =~ /^([0-9]+) (.+)/; + 20 <= $status and $status < 30 or die "gemini error: $status $meta"; + + if ($meta =~ "^text/gemini") { + my @lines; + if ($opts{abs_links}) { + $authority =~ m{/$} or $authority .= "/"; + $path =~ m{/$} or $path .= "/"; + for (<$cl>) { + s/\r?\n\z//; + if (m{^=> (?!/)} and not m{^=> [a-z]+://}) { + push @lines, "=> gemini://$authority$path$'"; + } elsif (m{^=> /}) { + push @lines, "=> gemini://$authority$'"; + } else { + push @lines, $_; + } + } + } else { + @lines = <$cl>; + } + push @lines, "" unless $lines[$#lines] eq ""; + push @lines, "Retrieved from $uri\n at " . localtime; + return $meta, \@lines; + } else { + return $meta, do { local $/; <$cl> }; + } +} + +sub gemtext_to_mail { + my ($gemtext, %opts) = @_; + $opts{from} or die "no From: address specified"; + $opts{to} or die "no To: address specified"; + + my $subject = $opts{link_title} // ""; + if ($gemtext->[0] =~ m{^#(?!#)\s*}) { + $subject = $'; + shift @$gemtext; + shift @$gemtext while $gemtext->[0] =~ /^$/; + } + + if ($opts{gemlog}) { + $opts{from} + = Mail::Field->new("From")->create($opts{from}, $opts{gemlog}) + ->stringify; + $subject = "$opts{gemlog}: $subject" if $subject; + } + + my $msg = MIME::Lite->new( + From => $opts{from}, + To => $opts{to}, + Subject => $subject, + Type => "multipart/mixed" + ); + $msg->add(Date => $opts{date}) if $opts{date}; + + my ($pre, @buffer); + my $flush = sub { + return unless @buffer; + $msg->attach(Type => "TEXT", Data => join "\r\n", @buffer); + undef @buffer; + }; + my $pad + = sub { push @buffer, "" unless !@buffer or $buffer[$#buffer] eq "" }; + for (@$gemtext) { + if ($pre) { + if (/^```/) { + $pre = 0; + } else { + push @buffer, " $_"; + } + } elsif (/^```/) { + &$pad; + $pre = 1; + } elsif (/^>\s*/) { + &$pad; + push @buffer, split "\n", wrap "> ", "> ", $'; + } elsif (/^\*\s*/) { + &$pad; + push @buffer, split "\n", wrap "• ", " ", $'; + } elsif ($opts{inline_images} + and my ($uri) = /^=>\s(\S+\.(?:jpg|jpeg|png|gif))\s/) { + &$flush; + my ($type, $data) = gemini_fetch($uri); + $msg->attach( + Type => $type, + Data => $data, + Filename => (split "/", $uri)[-1], + Disposition => "inline" + ); + } elsif (/^(?:=>|#+)/) { + &$pad; + push @buffer, $_; + } else { + &$pad; + push @buffer, split "\n", wrap "", "", $_; + } + } + + &$flush; + return $msg; +} -- cgit v1.2.3 From b23033238d8ae711cdb9bddcf6a1b21b2066fb6d Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Sat, 23 Jan 2021 18:11:51 -0700 Subject: gmi2email: refactor to avoid a level of nesting No functional change. Signed-off-by: Sean Whitton --- gmi2email | 53 ++++++++++++++++++++++++++--------------------------- 1 file changed, 26 insertions(+), 27 deletions(-) (limited to 'gmi2email') diff --git a/gmi2email b/gmi2email index c85728b..0d7615b 100755 --- a/gmi2email +++ b/gmi2email @@ -116,35 +116,34 @@ foreach my $sub (<$subs_fh>) { my ($rows) = $dbh->selectrow_array( "SELECT COUNT(*) FROM seen WHERE uri = \"$uri\""); - if ($rows == 0) { - my $mail = 1; - my ($type, $data); - #<<< - try { - ($type, $data) = gemini_fetch($uri, abs_links => 1); - } catch { - warn "when fetching $uri, $_"; - my ($code) = /"gemini error: ([1-6])/; - if ($code == 4) { - next; # try again next run - } else { - $mail = 0; # don't try this one again - } - }; - #>>> - $dbh->do("INSERT INTO seen VALUES (\"$uri\")"); - $mail or next; - if ($type =~ m{^text/gemini}) { - gemtext_to_mail( - $data, %to_mail_opts, - gemlog => $gemlog // "unknown gemlog", - link_title => $title, - date => email_date timelocal 0, - 0, 12, $d, $m - 1, $y - )->send; + next unless $rows == 0; + my $mail = 1; + my ($type, $data); + #<<< + try { + ($type, $data) = gemini_fetch($uri, abs_links => 1); + } catch { + warn "when fetching $uri, $_"; + my ($code) = /"gemini error: ([1-6])/; + if ($code == 4) { + next; # try again next run } else { - warn "$uri is not gemtext"; + $mail = 0; # don't try this one again } + }; + #>>> + $dbh->do("INSERT INTO seen VALUES (\"$uri\")"); + $mail or next; + if ($type =~ m{^text/gemini}) { + gemtext_to_mail( + $data, %to_mail_opts, + gemlog => $gemlog // "unknown gemlog", + link_title => $title, + date => email_date timelocal 0, + 0, 12, $d, $m - 1, $y + )->send; + } else { + warn "$uri is not gemtext"; } } } -- cgit v1.2.3 From c3b79c295706791b809024f5247fc366a7c78c53 Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Sat, 23 Jan 2021 18:15:24 -0700 Subject: gmi2email: avoid blank lines within lists of links Signed-off-by: Sean Whitton --- gmi2email | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'gmi2email') diff --git a/gmi2email b/gmi2email index 0d7615b..75816e6 100755 --- a/gmi2email +++ b/gmi2email @@ -257,9 +257,12 @@ sub gemtext_to_mail { Filename => (split "/", $uri)[-1], Disposition => "inline" ); - } elsif (/^(?:=>|#+)/) { - &$pad; + } elsif (/^=>/) { + &$pad unless @buffer and $buffer[$#buffer] =~ /^=>/; push @buffer, $_; + } elsif (/^#+/) { + &$pad; + push @buffer, $_; } else { &$pad; push @buffer, split "\n", wrap "", "", $_; -- cgit v1.2.3 From b8694107731a779b2e2ebd307577c8f1f6dc7c91 Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Sat, 23 Jan 2021 18:29:14 -0700 Subject: gmi2email: add --no-send Signed-off-by: Sean Whitton --- gmi2email | 9 +++++---- gmi2email.1.pod | 6 ++++++ 2 files changed, 11 insertions(+), 4 deletions(-) (limited to 'gmi2email') diff --git a/gmi2email b/gmi2email index 75816e6..67f9562 100755 --- a/gmi2email +++ b/gmi2email @@ -33,12 +33,13 @@ use Email::Date::Format "email_date"; use Time::Local; use Mail::Field::AddrList; -my ($from, $to, $do_subs, $inline_images); +my ($from, $to, $do_subs, $inline_images, $no_mail); GetOptions "from=s" => \$from, "to=s" => \$to, "subscriptions!" => \$do_subs, - "inline-images!" => \$inline_images; + "inline-images!" => \$inline_images, + "no-send!" => \$no_mail; my $conf_r = $ENV{XDG_CONFIG_HOME} || catfile $ENV{HOME}, ".config"; my $conf_f = catfile $conf_r, "mailscripts", "gmi2email.config"; @@ -74,7 +75,7 @@ for (@ARGV) { ($type, $data) = gemini_fetch($_, abs_links => 1); $type =~ m{^text/gemini} or die "$_ is not gemtext"; } - gemtext_to_mail($data, %to_mail_opts)->send; + $no_mail or gemtext_to_mail($data, %to_mail_opts)->send; } exit unless $do_subs; @@ -133,7 +134,7 @@ foreach my $sub (<$subs_fh>) { }; #>>> $dbh->do("INSERT INTO seen VALUES (\"$uri\")"); - $mail or next; + $mail and !$no_mail or next; if ($type =~ m{^text/gemini}) { gemtext_to_mail( $data, %to_mail_opts, diff --git a/gmi2email.1.pod b/gmi2email.1.pod index 5fa6d20..d4e656c 100644 --- a/gmi2email.1.pod +++ b/gmi2email.1.pod @@ -36,6 +36,12 @@ B<~/.config/mailscripts/gmi2email.subscriptions>. Download and inline any images included in the post. +=item B<--no-send> + +Don't actually send any mail. Intended when you just added some new +subscriptions and want to avoid receiving all the old posts you've already +read. + =item B<--from=>I
Set the From: address, overriding the configuration file. -- cgit v1.2.3 From 57384462f932628c66793f9ea5b13f98fd75035b Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Sat, 23 Jan 2021 18:29:29 -0700 Subject: gmi2email: don't attempt to inline images from outside of gemini:// Signed-off-by: Sean Whitton --- gmi2email | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gmi2email') diff --git a/gmi2email b/gmi2email index 67f9562..1b04883 100755 --- a/gmi2email +++ b/gmi2email @@ -249,7 +249,7 @@ sub gemtext_to_mail { &$pad; push @buffer, split "\n", wrap "• ", " ", $'; } elsif ($opts{inline_images} - and my ($uri) = /^=>\s(\S+\.(?:jpg|jpeg|png|gif))\s/) { + and my ($uri) = m{^=>\s(gemini://\S+\.(?:jpg|jpeg|png|gif))\s}) { &$flush; my ($type, $data) = gemini_fetch($uri); $msg->attach( -- cgit v1.2.3 From e004630d1e55006a4d8bce8dfea0658274f69d1b Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Sun, 24 Jan 2021 11:25:03 -0700 Subject: gmi2email: factor out send_subscribed_gemtext Signed-off-by: Sean Whitton --- gmi2email | 69 ++++++++++++++++++++++++++++++++++----------------------------- 1 file changed, 37 insertions(+), 32 deletions(-) (limited to 'gmi2email') diff --git a/gmi2email b/gmi2email index 1b04883..714e58a 100755 --- a/gmi2email +++ b/gmi2email @@ -114,42 +114,47 @@ foreach my $sub (<$subs_fh>) { chomp($gemlog = $'); } elsif (my ($uri, $y, $m, $d, $title) = /^=>\s*(\S+)\s+([0-9]{4})-([0-9]{2})-([0-9]{2})[\s-]*(.*)/) { - my ($rows) - = $dbh->selectrow_array( - "SELECT COUNT(*) FROM seen WHERE uri = \"$uri\""); - next unless $rows == 0; - my $mail = 1; - my ($type, $data); - #<<< - try { - ($type, $data) = gemini_fetch($uri, abs_links => 1); - } catch { - warn "when fetching $uri, $_"; - my ($code) = /"gemini error: ([1-6])/; - if ($code == 4) { - next; # try again next run - } else { - $mail = 0; # don't try this one again - } - }; - #>>> - $dbh->do("INSERT INTO seen VALUES (\"$uri\")"); - $mail and !$no_mail or next; - if ($type =~ m{^text/gemini}) { - gemtext_to_mail( - $data, %to_mail_opts, - gemlog => $gemlog // "unknown gemlog", - link_title => $title, - date => email_date timelocal 0, - 0, 12, $d, $m - 1, $y - )->send; - } else { - warn "$uri is not gemtext"; - } + send_subscribed_gemtext($uri, $gemlog // "unknown gemlog", + $title, timelocal 0, 0, 12, $d, $m - 1, $y); } } } +sub send_subscribed_gemtext { + my ($uri, $gemlog, $link_title, $feed_date) = @_; + my ($rows) + = $dbh->selectrow_array( + "SELECT COUNT(*) FROM seen WHERE uri = \"$uri\""); + next unless $rows == 0; + my $mail = 1; + my ($type, $data); + #<<< + try { + ($type, $data) = gemini_fetch($uri, abs_links => 1); + } catch { + warn "when fetching $uri, $_"; + my ($code) = /"gemini error: ([1-6])/; + if ($code == 4) { + return; # try again next run + } else { + $mail = 0; # don't try this one again + } + }; + #>>> + $dbh->do("INSERT INTO seen VALUES (\"$uri\")"); + $mail and !$no_mail or return; + if ($type =~ m{^text/gemini}) { + gemtext_to_mail( + $data, %to_mail_opts, + gemlog => $gemlog // "unknown gemlog", + link_title => $link_title, + date => email_date $feed_date + )->send; + } else { + warn "$uri is not gemtext"; + } +} + sub gemini_fetch { my ($uri, %opts) = @_; -- cgit v1.2.3 From d41a20368ed7c7458495cfbe2a13eab50f9c5c28 Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Sun, 24 Jan 2021 11:36:27 -0700 Subject: gmi2email: support for Atom feeds Signed-off-by: Sean Whitton --- debian/changelog | 4 ++-- debian/control | 1 + gmi2email | 25 +++++++++++++++++-------- gmi2email.1.pod | 5 +++-- 4 files changed, 23 insertions(+), 12 deletions(-) (limited to 'gmi2email') diff --git a/debian/changelog b/debian/changelog index bbb3bf5..5373cf1 100644 --- a/debian/changelog +++ b/debian/changelog @@ -2,8 +2,8 @@ mailscripts (0.23-1) UNRELEASED; urgency=medium * New script: gmi2email - add libdbd-sqlite3-perl, libio-socket-ssl-perl, libmime-lite-perl, - libemail-date-format-perl, libtry-tiny-perl and libmailtools-perl to - Recommends. + libemail-date-format-perl, libtry-tiny-perl, libmailtools-perl and + libxml-feed-perl to Recommends. -- Sean Whitton Sat, 23 Jan 2021 16:36:25 -0700 diff --git a/debian/control b/debian/control index a47be4c..552da66 100644 --- a/debian/control +++ b/debian/control @@ -57,6 +57,7 @@ Recommends: libemail-date-format-perl, libtry-tiny-perl, libmailtools-perl, + libxml-feed-perl, notmuch, python3-argcomplete, python3-gssapi, diff --git a/gmi2email b/gmi2email index 714e58a..dd9edba 100755 --- a/gmi2email +++ b/gmi2email @@ -32,6 +32,7 @@ use Getopt::Long; use Email::Date::Format "email_date"; use Time::Local; use Mail::Field::AddrList; +use XML::Feed; my ($from, $to, $do_subs, $inline_images, $no_mail); GetOptions @@ -108,15 +109,23 @@ foreach my $sub (<$subs_fh>) { } }; #>>> - $type =~ m{^text/gemini} or die "$sub is not gemtext"; - for (@$data) { - if (/^#\s*/ and not $gemlog) { - chomp($gemlog = $'); - } elsif (my ($uri, $y, $m, $d, $title) - = /^=>\s*(\S+)\s+([0-9]{4})-([0-9]{2})-([0-9]{2})[\s-]*(.*)/) { - send_subscribed_gemtext($uri, $gemlog // "unknown gemlog", - $title, timelocal 0, 0, 12, $d, $m - 1, $y); + if ($type =~ m{^text/gemini}) { + for (@$data) { + if (/^#\s*/ and not $gemlog) { + chomp($gemlog = $'); + } elsif (my ($uri, $y, $m, $d, $title) + = /^=>\s*(\S+)\s+([0-9]{4})-([0-9]{2})-([0-9]{2})[\s-]*(.*)/) { + send_subscribed_gemtext($uri, $gemlog // "unknown gemlog", + $title, timelocal 0, 0, 12, $d, $m - 1, $y); + } } + } elsif ($type =~ m{^text/xml}) { + my $feed = XML::Feed->parse(\$data); + send_subscribed_gemtext($_->link, $feed->title, $_->title, + ($_->issued // $_->modified)->epoch) + for $feed->entries; + } else { + die "$sub is not gemtext"; } } diff --git a/gmi2email.1.pod b/gmi2email.1.pod index d4e656c..d5004d5 100644 --- a/gmi2email.1.pod +++ b/gmi2email.1.pod @@ -23,8 +23,9 @@ text/gemini content specified on the command line. In addition to mailing any URIs/files specified on the command line, check subscribed gemlogs for new posts and send those too. Useful in a crontab. -Currently we only support the subscription mechanism described at -. +We support the subscription mechanism described at + as well as +Atom feeds. B looks for a file with a list of gemini:// URIs to check for new posts, one per line, in -- cgit v1.2.3 From 558d2566377ad7f6946129eab1f679e244b8999d Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Sun, 24 Jan 2021 11:51:29 -0700 Subject: gmi2email: improve image link regexp Signed-off-by: Sean Whitton --- gmi2email | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gmi2email') diff --git a/gmi2email b/gmi2email index dd9edba..e58532c 100755 --- a/gmi2email +++ b/gmi2email @@ -263,7 +263,7 @@ sub gemtext_to_mail { &$pad; push @buffer, split "\n", wrap "• ", " ", $'; } elsif ($opts{inline_images} - and my ($uri) = m{^=>\s(gemini://\S+\.(?:jpg|jpeg|png|gif))\s}) { + and my ($uri) = m{^=>\s*(gemini://\S+\.(?:jpg|jpeg|png|gif))}) { &$flush; my ($type, $data) = gemini_fetch($uri); $msg->attach( -- cgit v1.2.3 From eee09b348af9d8ca553687ede1525137a8ef734e Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Sun, 24 Jan 2021 12:04:14 -0700 Subject: gmi2email: don't exit subroutine via next Signed-off-by: Sean Whitton --- gmi2email | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gmi2email') diff --git a/gmi2email b/gmi2email index e58532c..4d9c2d3 100755 --- a/gmi2email +++ b/gmi2email @@ -134,7 +134,7 @@ sub send_subscribed_gemtext { my ($rows) = $dbh->selectrow_array( "SELECT COUNT(*) FROM seen WHERE uri = \"$uri\""); - next unless $rows == 0; + return unless $rows == 0; my $mail = 1; my ($type, $data); #<<< -- cgit v1.2.3 From cf42a5ec60a17f75fafbf8a91b17c794e48f5d02 Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Sun, 24 Jan 2021 12:06:00 -0700 Subject: gmi2email: accept application/xml MIME type Signed-off-by: Sean Whitton --- gmi2email | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gmi2email') diff --git a/gmi2email b/gmi2email index 4d9c2d3..063703a 100755 --- a/gmi2email +++ b/gmi2email @@ -119,7 +119,7 @@ foreach my $sub (<$subs_fh>) { $title, timelocal 0, 0, 12, $d, $m - 1, $y); } } - } elsif ($type =~ m{^text/xml}) { + } elsif ($type =~ m{^(?:text|application)/xml}) { my $feed = XML::Feed->parse(\$data); send_subscribed_gemtext($_->link, $feed->title, $_->title, ($_->issued // $_->modified)->epoch) -- cgit v1.2.3 From a18924ea980affccdac63b9b56daa594a97d1b64 Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Sun, 24 Jan 2021 12:10:37 -0700 Subject: gmi2email: accept application/atom+xml MIME type Signed-off-by: Sean Whitton --- gmi2email | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gmi2email') diff --git a/gmi2email b/gmi2email index 063703a..e0f4fa1 100755 --- a/gmi2email +++ b/gmi2email @@ -119,7 +119,7 @@ foreach my $sub (<$subs_fh>) { $title, timelocal 0, 0, 12, $d, $m - 1, $y); } } - } elsif ($type =~ m{^(?:text|application)/xml}) { + } elsif ($type =~ m{^(?:text|application)/(?:atom\+)?xml}) { my $feed = XML::Feed->parse(\$data); send_subscribed_gemtext($_->link, $feed->title, $_->title, ($_->issued // $_->modified)->epoch) -- cgit v1.2.3 From 7c523ab1153bb8cc4b31378ebe0563ef9813bdab Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Sun, 24 Jan 2021 12:10:51 -0700 Subject: gmi2email: improve error message Signed-off-by: Sean Whitton --- gmi2email | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gmi2email') diff --git a/gmi2email b/gmi2email index e0f4fa1..d0b2fa9 100755 --- a/gmi2email +++ b/gmi2email @@ -125,7 +125,7 @@ foreach my $sub (<$subs_fh>) { ($_->issued // $_->modified)->epoch) for $feed->entries; } else { - die "$sub is not gemtext"; + die "$sub is not gemtext or an Atom feed, so far as I can tell"; } } -- cgit v1.2.3 From 5b02197c3b928beb7b2de297df05a299ff188765 Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Sun, 24 Jan 2021 12:30:27 -0700 Subject: gmi2email: handle redirects Signed-off-by: Sean Whitton --- gmi2email | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'gmi2email') diff --git a/gmi2email b/gmi2email index d0b2fa9..8510643 100755 --- a/gmi2email +++ b/gmi2email @@ -182,8 +182,16 @@ sub gemini_fetch { ); print $cl "$uri\r\n"; - my ($status, $meta) = <$cl> =~ /^([0-9]+) (.+)/; - 20 <= $status and $status < 30 or die "gemini error: $status $meta"; + my ($status, $meta) = <$cl> =~ /^([0-9]+) (\V+)/; + if (30 <= $status and $status < 40) { + $opts{orig_uri} = $uri unless $opts{redirects}; + die "too many redirects while fetching $opts{orig_uri}" + if $opts{redirects} and $opts{redirects} > 5; + $opts{redirects}++; + return gemini_fetch($meta, %opts); + } elsif ($status < 20 or $status >= 40) { + die "gemini error: $status $meta"; + } if ($meta =~ "^text/gemini") { my @lines; -- cgit v1.2.3 From a9f38097bd9bf8af9bed3b4512618543a4330668 Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Sun, 24 Jan 2021 12:30:42 -0700 Subject: gmi2email: comment with shell snippet Signed-off-by: Sean Whitton --- gmi2email | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'gmi2email') diff --git a/gmi2email b/gmi2email index 8510643..9cff323 100755 --- a/gmi2email +++ b/gmi2email @@ -17,6 +17,14 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +# TESTING/DEVEL +# +# To forget about seen entries of a feed: +# +# % perl -MDBI +# -we'DBI->connect("dbi:SQLite:dbname=$ENV{HOME}/.cache/mailscripts/gmi2email.db", +# "", "")->do("DELETE FROM seen WHERE uri LIKE \"gemini://example.com/%\"")' + use 5.028; use strict; use warnings; -- cgit v1.2.3 From 0245e5f9c57b40be31228dddc1efabea3f9c7b3a Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Sun, 24 Jan 2021 12:54:04 -0700 Subject: gmi2email: tweak error message Signed-off-by: Sean Whitton --- gmi2email | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gmi2email') diff --git a/gmi2email b/gmi2email index 9cff323..4fccb38 100755 --- a/gmi2email +++ b/gmi2email @@ -133,7 +133,7 @@ foreach my $sub (<$subs_fh>) { ($_->issued // $_->modified)->epoch) for $feed->entries; } else { - die "$sub is not gemtext or an Atom feed, so far as I can tell"; + die "$sub is not gemtext nor an Atom feed, so far as I can tell"; } } -- cgit v1.2.3 From 0552ba0301456c5137081665315e93d1eca7198c Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Sun, 24 Jan 2021 18:54:03 -0700 Subject: gmi2email: tweak error message Signed-off-by: Sean Whitton --- gmi2email | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gmi2email') diff --git a/gmi2email b/gmi2email index 4fccb38..a124099 100755 --- a/gmi2email +++ b/gmi2email @@ -55,7 +55,7 @@ my $conf_f = catfile $conf_r, "mailscripts", "gmi2email.config"; -e $conf_f or (defined $to and defined $from) or die - "no config file or sufficient command line options: don't know who to mail"; + "no config file nor sufficient command line options: don't know who to mail"; my $conf = Config::Tiny->new->read($conf_f); my %to_mail_opts = ( -- cgit v1.2.3 From 77740f4625825f605258fa706069efff52b511d9 Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Mon, 25 Jan 2021 10:52:19 -0700 Subject: gmi2email: tweak error message Signed-off-by: Sean Whitton --- gmi2email | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gmi2email') diff --git a/gmi2email b/gmi2email index a124099..f03c930 100755 --- a/gmi2email +++ b/gmi2email @@ -198,7 +198,7 @@ sub gemini_fetch { $opts{redirects}++; return gemini_fetch($meta, %opts); } elsif ($status < 20 or $status >= 40) { - die "gemini error: $status $meta"; + die "while fetching $uri: gemini error: $status $meta"; } if ($meta =~ "^text/gemini") { -- cgit v1.2.3 From 3872d4850ca9ade0eccc8c7054b6459a1d00b25f Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Mon, 25 Jan 2021 19:39:58 -0700 Subject: gmi2email: don't record as seen if sending mail threw an exception Signed-off-by: Sean Whitton --- gmi2email | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'gmi2email') diff --git a/gmi2email b/gmi2email index f03c930..cae0767 100755 --- a/gmi2email +++ b/gmi2email @@ -158,18 +158,17 @@ sub send_subscribed_gemtext { } }; #>>> - $dbh->do("INSERT INTO seen VALUES (\"$uri\")"); - $mail and !$no_mail or return; if ($type =~ m{^text/gemini}) { gemtext_to_mail( $data, %to_mail_opts, gemlog => $gemlog // "unknown gemlog", link_title => $link_title, date => email_date $feed_date - )->send; + )->send if $mail and !$no_mail; } else { warn "$uri is not gemtext"; } + $dbh->do("INSERT INTO seen VALUES (\"$uri\")"); } sub gemini_fetch { -- cgit v1.2.3 From f43a42a8c5687057ff28e884863d7a5a877837f3 Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Mon, 25 Jan 2021 19:40:42 -0700 Subject: gmi2email: drop superfluous chomp Signed-off-by: Sean Whitton --- gmi2email | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gmi2email') diff --git a/gmi2email b/gmi2email index cae0767..5b8ba4a 100755 --- a/gmi2email +++ b/gmi2email @@ -120,7 +120,7 @@ foreach my $sub (<$subs_fh>) { if ($type =~ m{^text/gemini}) { for (@$data) { if (/^#\s*/ and not $gemlog) { - chomp($gemlog = $'); + $gemlog = $'; } elsif (my ($uri, $y, $m, $d, $title) = /^=>\s*(\S+)\s+([0-9]{4})-([0-9]{2})-([0-9]{2})[\s-]*(.*)/) { send_subscribed_gemtext($uri, $gemlog // "unknown gemlog", -- cgit v1.2.3 From 16b42aa5983e022e4662bad333765d284e52b61b Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Wed, 27 Jan 2021 16:25:26 -0700 Subject: gmi2email: perltidy Signed-off-by: Sean Whitton --- gmi2email | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) (limited to 'gmi2email') diff --git a/gmi2email b/gmi2email index 5b8ba4a..4c567e9 100755 --- a/gmi2email +++ b/gmi2email @@ -164,7 +164,8 @@ sub send_subscribed_gemtext { gemlog => $gemlog // "unknown gemlog", link_title => $link_title, date => email_date $feed_date - )->send if $mail and !$no_mail; + )->send + if $mail and !$no_mail; } else { warn "$uri is not gemtext"; } @@ -191,13 +192,13 @@ sub gemini_fetch { my ($status, $meta) = <$cl> =~ /^([0-9]+) (\V+)/; if (30 <= $status and $status < 40) { - $opts{orig_uri} = $uri unless $opts{redirects}; + $opts{orig_uri} = $uri unless $opts{redirects}; die "too many redirects while fetching $opts{orig_uri}" if $opts{redirects} and $opts{redirects} > 5; - $opts{redirects}++; - return gemini_fetch($meta, %opts); + $opts{redirects}++; + return gemini_fetch($meta, %opts); } elsif ($status < 20 or $status >= 40) { - die "while fetching $uri: gemini error: $status $meta"; + die "while fetching $uri: gemini error: $status $meta"; } if ($meta =~ "^text/gemini") { @@ -290,9 +291,9 @@ sub gemtext_to_mail { } elsif (/^=>/) { &$pad unless @buffer and $buffer[$#buffer] =~ /^=>/; push @buffer, $_; - } elsif (/^#+/) { - &$pad; - push @buffer, $_; + } elsif (/^#+/) { + &$pad; + push @buffer, $_; } else { &$pad; push @buffer, split "\n", wrap "", "", $_; -- cgit v1.2.3 From 1f07bf968d154664cfec5d2414b8a960f82e38ec Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Wed, 27 Jan 2021 16:27:14 -0700 Subject: gmi2email: enable specifying a list of subscriptions Signed-off-by: Sean Whitton --- gmi2email | 24 ++++++++++++------------ gmi2email.1.pod | 4 ++-- 2 files changed, 14 insertions(+), 14 deletions(-) (limited to 'gmi2email') diff --git a/gmi2email b/gmi2email index 4c567e9..54a3459 100755 --- a/gmi2email +++ b/gmi2email @@ -42,13 +42,13 @@ use Time::Local; use Mail::Field::AddrList; use XML::Feed; -my ($from, $to, $do_subs, $inline_images, $no_mail); +my ($from, $to, $subs, $inline_images, $no_mail); GetOptions - "from=s" => \$from, - "to=s" => \$to, - "subscriptions!" => \$do_subs, - "inline-images!" => \$inline_images, - "no-send!" => \$no_mail; + "from=s" => \$from, + "to=s" => \$to, + "subscriptions:s" => \$subs, + "inline-images!" => \$inline_images, + "no-send!" => \$no_mail; my $conf_r = $ENV{XDG_CONFIG_HOME} || catfile $ENV{HOME}, ".config"; my $conf_f = catfile $conf_r, "mailscripts", "gmi2email.config"; @@ -57,6 +57,8 @@ my $conf_f = catfile $conf_r, "mailscripts", "gmi2email.config"; or die "no config file nor sufficient command line options: don't know who to mail"; my $conf = Config::Tiny->new->read($conf_f); +$subs ||= catfile $conf_r, "mailscripts", "gmi2email.subscriptions" + if defined $subs; my %to_mail_opts = ( from => ( @@ -72,7 +74,7 @@ my %to_mail_opts = ( inline_images => $inline_images // $conf->{_}->{inline_images} // 0 ); -@ARGV or $do_subs or die "nothing to do\n"; +@ARGV or $subs or die "nothing to do\n"; for (@ARGV) { my $data; @@ -87,11 +89,9 @@ for (@ARGV) { $no_mail or gemtext_to_mail($data, %to_mail_opts)->send; } -exit unless $do_subs; - -my $subs_f = catfile $conf_r, "mailscripts", "gmi2email.subscriptions"; --e $subs_f or die "no list of subscriptions"; -open my $subs_fh, "<", $subs_f; +exit unless $subs; +-r $subs or die "file $subs not readable"; +open my $subs_fh, "<", $subs; my $db_r = $ENV{XDG_CACHE_HOME} || catfile $ENV{HOME}, ".cache"; my $db_d = catfile $db_r, "mailscripts"; diff --git a/gmi2email.1.pod b/gmi2email.1.pod index 6f12b84..b7e9108 100644 --- a/gmi2email.1.pod +++ b/gmi2email.1.pod @@ -59,7 +59,7 @@ URIs, e.g. =over 4 -=item B<--subscriptions> +=item B<--subscriptions>[=I] In addition to mailing any URIs/files specified on the command line, check subscribed gemlogs for new posts and send those too. Useful in a crontab. @@ -69,7 +69,7 @@ We support the subscription mechanism described at Atom feeds. B looks for a file with a list of gemini:// URIs to check for new -posts, one per line, in +posts, one per line, in I, or if that is not set, in B<$XDG_CONFIG_HOME/mailscripts/gmi2email.subscriptions>, or if XDG_CONFIG_HOME is not set, it falls back to trying to read B<~/.config/mailscripts/gmi2email.subscriptions>. -- cgit v1.2.3 From 2226a0b70fa795771f1eff94063d37e0477a3e4e Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Wed, 27 Jan 2021 17:07:39 -0700 Subject: gmi2email: don't exit subroutine via 'next' Signed-off-by: Sean Whitton --- gmi2email | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'gmi2email') diff --git a/gmi2email b/gmi2email index 54a3459..bb880a5 100755 --- a/gmi2email +++ b/gmi2email @@ -103,7 +103,7 @@ $dbh->do("CREATE TABLE IF NOT EXISTS seen (uri TEXT PRIMARY KEY)") foreach my $sub (<$subs_fh>) { chomp $sub; - my ($gemlog, $type, $data); + my ($gemlog, $type, $data, $next); #<<< try { ($type, $data) = gemini_fetch($sub, abs_links => 1); @@ -111,12 +111,13 @@ foreach my $sub (<$subs_fh>) { my ($code) = /"gemini error: ([1-6])/; if ($code == 4) { warn "temporary failure retrieving $sub"; - next; # try again next run + $next = 1, return; # try again next run } else { die "while retrieving $sub $_"; } }; #>>> + next if $next; if ($type =~ m{^text/gemini}) { for (@$data) { if (/^#\s*/ and not $gemlog) { -- cgit v1.2.3 From e56b457674c0b2ef5743998611c68d364eeba6de Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Wed, 27 Jan 2021 17:07:49 -0700 Subject: gmi2email: die if did not get gemini response Signed-off-by: Sean Whitton --- gmi2email | 2 ++ 1 file changed, 2 insertions(+) (limited to 'gmi2email') diff --git a/gmi2email b/gmi2email index bb880a5..d2477ac 100755 --- a/gmi2email +++ b/gmi2email @@ -192,6 +192,8 @@ sub gemini_fetch { print $cl "$uri\r\n"; my ($status, $meta) = <$cl> =~ /^([0-9]+) (\V+)/; + defined $status and defined $meta + or die "while fetching $uri: missing or invalid gemini response\n"; if (30 <= $status and $status < 40) { $opts{orig_uri} = $uri unless $opts{redirects}; die "too many redirects while fetching $opts{orig_uri}" -- cgit v1.2.3 From cd604d24886a22b4b7eb1a40b2b9d5be8701aca9 Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Wed, 27 Jan 2021 17:08:19 -0700 Subject: gmi2email: treat missing gemini response as a temporary failure Signed-off-by: Sean Whitton --- gmi2email | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gmi2email') diff --git a/gmi2email b/gmi2email index d2477ac..c3e26e5 100755 --- a/gmi2email +++ b/gmi2email @@ -109,7 +109,7 @@ foreach my $sub (<$subs_fh>) { ($type, $data) = gemini_fetch($sub, abs_links => 1); } catch { my ($code) = /"gemini error: ([1-6])/; - if ($code == 4) { + if (defined $code and $code == 4 or /missing or invalid gemini response/) { warn "temporary failure retrieving $sub"; $next = 1, return; # try again next run } else { -- cgit v1.2.3 From 293af10a9bd6acfee58b15c8f5a709f888aead58 Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Wed, 27 Jan 2021 17:38:32 -0700 Subject: gmi2email: die if fail to establish SSL connection Signed-off-by: Sean Whitton --- gmi2email | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gmi2email') diff --git a/gmi2email b/gmi2email index c3e26e5..02e3d66 100755 --- a/gmi2email +++ b/gmi2email @@ -188,7 +188,7 @@ sub gemini_fetch { PeerHost => $host, PeerPort => $port // 1965, SSL_verify_mode => SSL_VERIFY_NONE - ); + ) or die "while fetching $uri: failed to establish SSL connection\n"; print $cl "$uri\r\n"; my ($status, $meta) = <$cl> =~ /^([0-9]+) (\V+)/; -- cgit v1.2.3 From 344694d50aa941e90c09108e453a17b273130c14 Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Wed, 27 Jan 2021 17:39:01 -0700 Subject: gmi2email: cope with feed entries which don't have dates Signed-off-by: Sean Whitton --- gmi2email | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'gmi2email') diff --git a/gmi2email b/gmi2email index 02e3d66..716a866 100755 --- a/gmi2email +++ b/gmi2email @@ -130,9 +130,11 @@ foreach my $sub (<$subs_fh>) { } } elsif ($type =~ m{^(?:text|application)/(?:atom\+)?xml}) { my $feed = XML::Feed->parse(\$data); - send_subscribed_gemtext($_->link, $feed->title, $_->title, - ($_->issued // $_->modified)->epoch) - for $feed->entries; + for ($feed->entries) { + my $date = $_->issued // $_->modified; + $date = $date->epoch if $date; + send_subscribed_gemtext($_->link, $feed->title, $_->title, $date); + } } else { die "$sub is not gemtext nor an Atom feed, so far as I can tell"; } @@ -164,7 +166,7 @@ sub send_subscribed_gemtext { $data, %to_mail_opts, gemlog => $gemlog // "unknown gemlog", link_title => $link_title, - date => email_date $feed_date + date => email_date $feed_date // time )->send if $mail and !$no_mail; } else { -- cgit v1.2.3 From f95a05571afe962dc9aa1ffe6995444c06026910 Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Wed, 27 Jan 2021 17:39:44 -0700 Subject: gmi2email: treat SSL connection failure as a temporary failure Signed-off-by: Sean Whitton --- gmi2email | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'gmi2email') diff --git a/gmi2email b/gmi2email index 716a866..6b8bd6c 100755 --- a/gmi2email +++ b/gmi2email @@ -109,7 +109,9 @@ foreach my $sub (<$subs_fh>) { ($type, $data) = gemini_fetch($sub, abs_links => 1); } catch { my ($code) = /"gemini error: ([1-6])/; - if (defined $code and $code == 4 or /missing or invalid gemini response/) { + if ( defined $code and $code == 4 + or /missing or invalid gemini response/ + or /failed to establish SSL connection/) { warn "temporary failure retrieving $sub"; $next = 1, return; # try again next run } else { -- cgit v1.2.3 From b8c5591bf83da92138369e2c135be95115bec009 Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Wed, 27 Jan 2021 17:41:09 -0700 Subject: gmi2email: tweak error message Signed-off-by: Sean Whitton --- gmi2email | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gmi2email') diff --git a/gmi2email b/gmi2email index 6b8bd6c..89c9f13 100755 --- a/gmi2email +++ b/gmi2email @@ -112,7 +112,7 @@ foreach my $sub (<$subs_fh>) { if ( defined $code and $code == 4 or /missing or invalid gemini response/ or /failed to establish SSL connection/) { - warn "temporary failure retrieving $sub"; + warn "temporary failure retrieving $sub; will try again later:\n $_"; $next = 1, return; # try again next run } else { die "while retrieving $sub $_"; -- cgit v1.2.3 From f9370aec2aa8702bb7826535a2f9409ff0f44040 Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Wed, 27 Jan 2021 17:43:42 -0700 Subject: gmi2email: perltidy Signed-off-by: Sean Whitton --- gmi2email | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'gmi2email') diff --git a/gmi2email b/gmi2email index 89c9f13..ed0fb28 100755 --- a/gmi2email +++ b/gmi2email @@ -132,11 +132,11 @@ foreach my $sub (<$subs_fh>) { } } elsif ($type =~ m{^(?:text|application)/(?:atom\+)?xml}) { my $feed = XML::Feed->parse(\$data); - for ($feed->entries) { - my $date = $_->issued // $_->modified; - $date = $date->epoch if $date; - send_subscribed_gemtext($_->link, $feed->title, $_->title, $date); - } + for ($feed->entries) { + my $date = $_->issued // $_->modified; + $date = $date->epoch if $date; + send_subscribed_gemtext($_->link, $feed->title, $_->title, $date); + } } else { die "$sub is not gemtext nor an Atom feed, so far as I can tell"; } -- cgit v1.2.3 From f83c52007dde3fa3b243a0fe3ac6bb052b963b71 Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Wed, 27 Jan 2021 18:10:17 -0700 Subject: gmi2email: avoid double slashes Seems some servers don't eliminate these themselves and can return "Not found". Signed-off-by: Sean Whitton --- gmi2email | 1 + 1 file changed, 1 insertion(+) (limited to 'gmi2email') diff --git a/gmi2email b/gmi2email index ed0fb28..1015328 100755 --- a/gmi2email +++ b/gmi2email @@ -211,6 +211,7 @@ sub gemini_fetch { if ($meta =~ "^text/gemini") { my @lines; if ($opts{abs_links}) { + $path =~ s{^/}{}; $authority =~ m{/$} or $authority .= "/"; $path =~ m{/$} or $path .= "/"; for (<$cl>) { -- cgit v1.2.3 From 891877f7b1bbb1dea0f82ec6e577dc0cb3ce51e0 Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Wed, 27 Jan 2021 18:10:46 -0700 Subject: gmi2email: handle ./ links Signed-off-by: Sean Whitton --- gmi2email | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'gmi2email') diff --git a/gmi2email b/gmi2email index 1015328..9fa4c23 100755 --- a/gmi2email +++ b/gmi2email @@ -212,11 +212,14 @@ sub gemini_fetch { my @lines; if ($opts{abs_links}) { $path =~ s{^/}{}; + (my $dir = $path) =~ s{[^/]*$}{}; $authority =~ m{/$} or $authority .= "/"; $path =~ m{/$} or $path .= "/"; for (<$cl>) { s/\r?\n\z//; - if (m{^=> (?!/)} and not m{^=> [a-z]+://}) { + if (m{^=>\s*\./}) { + push @lines, "=> gemini://$authority$dir$'"; + } elsif (m{^=> (?!/)} and not m{^=> [a-z]+://}) { push @lines, "=> gemini://$authority$path$'"; } elsif (m{^=> /}) { push @lines, "=> gemini://$authority$'"; -- cgit v1.2.3 From c97c00d799bbfbab958d3b03d403c5431a1cb7e2 Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Thu, 28 Jan 2021 00:16:18 -0700 Subject: gmi2email: avoid loading whole gemtext into memory Signed-off-by: Sean Whitton --- gmi2email | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gmi2email') diff --git a/gmi2email b/gmi2email index 9fa4c23..d3e0ed5 100755 --- a/gmi2email +++ b/gmi2email @@ -215,7 +215,7 @@ sub gemini_fetch { (my $dir = $path) =~ s{[^/]*$}{}; $authority =~ m{/$} or $authority .= "/"; $path =~ m{/$} or $path .= "/"; - for (<$cl>) { + while (local $_ = <$cl>) { s/\r?\n\z//; if (m{^=>\s*\./}) { push @lines, "=> gemini://$authority$dir$'"; -- cgit v1.2.3 From f42d49b2939ab6983271a7ecdcf16a996f6ebeac Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Thu, 28 Jan 2021 00:16:49 -0700 Subject: gmi2email: handle failures to fetch images Signed-off-by: Sean Whitton --- gmi2email | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'gmi2email') diff --git a/gmi2email b/gmi2email index d3e0ed5..ef38426 100755 --- a/gmi2email +++ b/gmi2email @@ -292,13 +292,21 @@ sub gemtext_to_mail { } elsif ($opts{inline_images} and my ($uri) = m{^=>\s*(gemini://\S+\.(?:jpg|jpeg|png|gif))}) { &$flush; - my ($type, $data) = gemini_fetch($uri); + my ($type, $data, $failed); + #<<< + try { + ($type, $data) = gemini_fetch($uri); + } catch { + push @buffer, "when fetching $uri, $_"; + $failed = 1; + }; + #>>> $msg->attach( Type => $type, Data => $data, Filename => (split "/", $uri)[-1], Disposition => "inline" - ); + ) unless $failed; } elsif (/^=>/) { &$pad unless @buffer and $buffer[$#buffer] =~ /^=>/; push @buffer, $_; -- cgit v1.2.3 From 9cb043b4cf9b273a170430b566135fde72d9ef55 Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Thu, 28 Jan 2021 00:17:01 -0700 Subject: gmi2email: fix handling relative links Signed-off-by: Sean Whitton --- gmi2email | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) (limited to 'gmi2email') diff --git a/gmi2email b/gmi2email index ef38426..f4a13a6 100755 --- a/gmi2email +++ b/gmi2email @@ -211,17 +211,13 @@ sub gemini_fetch { if ($meta =~ "^text/gemini") { my @lines; if ($opts{abs_links}) { - $path =~ s{^/}{}; - (my $dir = $path) =~ s{[^/]*$}{}; + my $dir = $path =~ s{[^/]*$}{}r =~ s{^/}{}r; $authority =~ m{/$} or $authority .= "/"; - $path =~ m{/$} or $path .= "/"; while (local $_ = <$cl>) { s/\r?\n\z//; - if (m{^=>\s*\./}) { + if (m{^=>\s*\./} || m{^=>\s*(?!/)} and not m{^=> [a-z]+://}) { push @lines, "=> gemini://$authority$dir$'"; - } elsif (m{^=> (?!/)} and not m{^=> [a-z]+://}) { - push @lines, "=> gemini://$authority$path$'"; - } elsif (m{^=> /}) { + } elsif (m{^=>\s*/}) { push @lines, "=> gemini://$authority$'"; } else { push @lines, $_; -- cgit v1.2.3 From f86afc9f4f372d0322d693e9b767d924e08a7682 Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Thu, 28 Jan 2021 16:02:39 -0700 Subject: gmi2email: cope with links in feeds missing the protocol Signed-off-by: Sean Whitton --- gmi2email | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) (limited to 'gmi2email') diff --git a/gmi2email b/gmi2email index f4a13a6..0d9861b 100755 --- a/gmi2email +++ b/gmi2email @@ -135,7 +135,17 @@ foreach my $sub (<$subs_fh>) { for ($feed->entries) { my $date = $_->issued // $_->modified; $date = $date->epoch if $date; - send_subscribed_gemtext($_->link, $feed->title, $_->title, $date); + + my $link; + if ($_->link =~ m{^//}) { + $link = "gemini:" . $_->link; + } elsif ($_->link !~ m{^[a-z]+://}) { + $link = "gemini://" . $_->link; + } else { + $link = $_->link; + } + + send_subscribed_gemtext($link, $feed->title, $_->title, $date); } } else { die "$sub is not gemtext nor an Atom feed, so far as I can tell"; -- cgit v1.2.3 From 182d28e47858c183d3be2e172c400e3b0521167f Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Thu, 28 Jan 2021 16:03:03 -0700 Subject: gmi2email: avoid some uninitialised variables Signed-off-by: Sean Whitton --- gmi2email | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'gmi2email') diff --git a/gmi2email b/gmi2email index 0d9861b..fcf0f0e 100755 --- a/gmi2email +++ b/gmi2email @@ -166,14 +166,14 @@ sub send_subscribed_gemtext { } catch { warn "when fetching $uri, $_"; my ($code) = /"gemini error: ([1-6])/; - if ($code == 4) { + if ($code and $code == 4) { return; # try again next run } else { $mail = 0; # don't try this one again } }; #>>> - if ($type =~ m{^text/gemini}) { + if ($type and $type =~ m{^text/gemini}) { gemtext_to_mail( $data, %to_mail_opts, gemlog => $gemlog // "unknown gemlog", @@ -236,7 +236,7 @@ sub gemini_fetch { } else { @lines = <$cl>; } - push @lines, "" unless $lines[$#lines] eq ""; + push @lines, "" unless !@lines or $lines[$#lines] eq ""; push @lines, "Retrieved from $uri\n at " . localtime; return $meta, \@lines; } else { -- cgit v1.2.3 From 30fda961a6662eff16e06fc2f1b6fd023ba3d8b4 Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Thu, 28 Jan 2021 16:03:15 -0700 Subject: gmi2email: accept more MIME types Signed-off-by: Sean Whitton --- gmi2email | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gmi2email') diff --git a/gmi2email b/gmi2email index fcf0f0e..27249fa 100755 --- a/gmi2email +++ b/gmi2email @@ -130,7 +130,7 @@ foreach my $sub (<$subs_fh>) { $title, timelocal 0, 0, 12, $d, $m - 1, $y); } } - } elsif ($type =~ m{^(?:text|application)/(?:atom\+)?xml}) { + } elsif ($type =~ m{^(?:text|application)/(?:(?:atom|rss)\+)?xml}) { my $feed = XML::Feed->parse(\$data); for ($feed->entries) { my $date = $_->issued // $_->modified; -- cgit v1.2.3 From 8a0d58491ce203fe33fc3071acf6866c0986aa26 Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Thu, 28 Jan 2021 16:03:25 -0700 Subject: gmi2email: cope with .. in links Signed-off-by: Sean Whitton --- gmi2email | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'gmi2email') diff --git a/gmi2email b/gmi2email index 27249fa..cbbded4 100755 --- a/gmi2email +++ b/gmi2email @@ -226,7 +226,10 @@ sub gemini_fetch { while (local $_ = <$cl>) { s/\r?\n\z//; if (m{^=>\s*\./} || m{^=>\s*(?!/)} and not m{^=> [a-z]+://}) { - push @lines, "=> gemini://$authority$dir$'"; + my $link = "$dir$'"; + # attempt to resolve any use of '..' notation + 1 while $link =~ s{/[^/]+/../}{/}; + push @lines, "=> gemini://$authority$link"; } elsif (m{^=>\s*/}) { push @lines, "=> gemini://$authority$'"; } else { -- cgit v1.2.3