# Copyright (C) 2021 all contributors
# License: AGPL-3.0+
# lcat: local cat, display a local message by Message-ID or blob,
# extracting from URL necessary
# "lei lcat "
package PublicInbox::LeiLcat;
use strict;
use v5.10.1;
use PublicInbox::LeiViewText;
use URI::Escape qw(uri_unescape);
use PublicInbox::MID qw($MID_EXTRACT);
sub lcat_folder ($$$) {
my ($lei, $lms, $folder) = @_;
$lms //= $lei->{lse}->lms // return;
my $folders = [ $folder];
my $err = $lms->arg2folder($lei, $folders);
$lei->qerr(@{$err->{qerr}}) if $err && $err->{qerr};
if ($err && $err->{fail}) {
$lei->child_error(1 << 8, "# unknown folder: $folder");
} else {
for my $f (@$folders) {
my $fid = $lms->fid_for($f);
push @{$lei->{lcat_fid}}, $fid;
}
}
}
sub lcat_imap_uri ($$) {
my ($lei, $uri) = @_;
my $lms = $lei->{lse}->lms or return;
# cf. LeiXsearch->lcat_dump
if (defined $uri->uid) {
my $oidhex = $lms->imap_oid($lei, $uri);
if (ref(my $err = $oidhex)) { # art2folder error
$lei->qerr(@{$err->{qerr}}) if $err->{qerr};
}
push @{$lei->{lcat_blob}}, $oidhex;
} elsif (defined(my $fid = $lms->fid_for($$uri))) {
push @{$lei->{lcat_fid}}, $fid;
} else {
lcat_folder($lei, $lms, $$uri);
}
}
sub extract_1 ($$) {
my ($lei, $x) = @_;
if ($x =~ m!\b(imaps?://[^>]+)!i) {
my $u = $1;
require PublicInbox::URIimap;
lcat_imap_uri($lei, PublicInbox::URIimap->new($u));
'""'; # blank query, using {lcat_blob} or {lcat_fid}
} elsif ($x =~ m!\b(maildir:.+)!i) {
lcat_folder($lei, undef, $1);
'""'; # blank query, using {lcat_blob} or {lcat_fid}
} elsif ($x =~ m!\b([a-z]+?://\S+)!i) {
my $u = $1;
$u =~ s/[\>\]\)\,\.\;]+\z//;
require URI;
$u = URI->new($u);
my $p = $u->path;
my $term;
if ($p =~ m!([^/]+\@[^/]+)!) { # common msgid pattern
$term = 'mid:'.uri_unescape($1);
# is it a URL which returns the full thread?
if ($u->scheme =~ /\Ahttps?/i &&
$p =~ m!/(?:T/?|t/?|t\.mbox\.gz|t\.atom)\b!) {
$lei->{mset_opt}->{threads} = 1;
}
} elsif ($u->scheme =~ /\Ahttps?/i &&
# some msgids don't have '@', see if it looks like
# a public-inbox URL:
$p =~ m!/([^/]+)/(raw|t/?|T/?|
t\.mbox\.gz|t\.atom)\z!x) {
$lei->{mset_opt}->{threads} = 1 if $2 && $2 ne 'raw';
$term = 'mid:'.uri_unescape($1);
}
$term;
} elsif ($x =~ $MID_EXTRACT) { # <$MSGID>
"mid:$1";
} elsif ($x =~ /\b((?:m|mid):\S+)/) { # our own prefixes (and mairix)
$1;
} elsif ($x =~ /\bid:(\S+)/) { # notmuch convention
"mid:$1";
} elsif ($x =~ /\bblob:([0-9a-f]{7,})\b/) {
push @{$lei->{lcat_blob}}, $1; # cf. LeiToMail->wq_atexit_child
'""'; # blank query
} else {
undef;
}
}
sub extract_all {
my ($lei, @argv) = @_;
my $strict = !$lei->{opt}->{stdin};
my @q;
for my $x (@argv) {
if (my $term = extract_1($lei,$x)) {
push @q, $term;
} elsif ($strict) {
return $lei->fail(<<"");
could not extract Message-ID from $x
}
}
@q ? join(' OR ', @q) : $lei->fail("no Message-ID in: @argv");
}
sub _stdin { # PublicInbox::InputPipe::consume callback for --stdin
my ($lei) = @_; # $_[1] = $rbuf
if (defined($_[1])) {
$_[1] eq '' and return eval {
$lei->fchdir or return;
my @argv = split(/\s+/, $lei->{mset_opt}->{qstr});
$lei->{mset_opt}->{qstr} = extract_all($lei, @argv)
or return;
$lei->_start_query;
};
$lei->{mset_opt}->{qstr} .= $_[1];
} else {
$lei->fail("error reading stdin: $!");
}
}
sub lei_lcat {
my ($lei, @argv) = @_;
my $lxs = $lei->lxs_prepare or return;
$lei->ale->refresh_externals($lxs);
my $sto = $lei->_lei_store(1);
$lei->{lse} = $sto->search;
my $opt = $lei->{opt};
my %mset_opt = map { $_ => $opt->{$_} } qw(threads limit offset);
$mset_opt{asc} = $opt->{'reverse'} ? 1 : 0;
$mset_opt{limit} //= 10000;
$opt->{sort} //= 'relevance';
$mset_opt{relevance} = 1;
$lei->{mset_opt} = \%mset_opt;
$opt->{'format'} //= 'text' unless defined($opt->{output});
if ($lei->{opt}->{stdin}) {
return $lei->fail(<<'') if @argv;
no args allowed on command-line with --stdin
require PublicInbox::InputPipe;
PublicInbox::InputPipe::consume($lei->{0}, \&_stdin, $lei);
return;
}
$lei->{mset_opt}->{qstr} = extract_all($lei, @argv) or return;
$lei->_start_query;
}
sub _complete_lcat {
my ($lei, @argv) = @_;
my $sto = $lei->_lei_store or return;
my $lms = $sto->search->lms or return;
my $match_cb = $lei->complete_url_prepare(\@argv);
map { $match_cb->($_) } $lms->folders;
}
1;