1 # Copyright (C) 2021 all contributors <meta@public-inbox.org>
2 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
4 # lcat: local cat, display a local message by Message-ID or blob,
5 # extracting from URL necessary
6 # "lei lcat <URL|SPEC>"
7 package PublicInbox::LeiLcat;
10 use PublicInbox::LeiViewText;
11 use URI::Escape qw(uri_unescape);
12 use PublicInbox::MID qw($MID_EXTRACT);
14 sub lcat_folder ($$;$$) {
15 my ($lei, $folder, $beg, $end) = @_;
16 my $lms = $lei->{-lms_ro} //= $lei->lms // return;
17 my $folders = [ $folder ];
18 eval { $lms->arg2folder($lei, $folders) };
19 return $lei->child_error(0, "# unknown folder: $folder") if $@;
21 if (defined($beg)) { # NNTP article range
23 $range{max} = $end // $beg;
25 for my $f (@$folders) {
26 my $fid = $lms->fid_for($f);
27 push @{$lei->{lcat_todo}}, { fid => $fid, %range };
31 sub lcat_imap_uri ($$) {
33 # cf. LeiXSearch->lcat_dump
34 my $lms = $lei->{-lms_ro} //= $lei->lms // return;
35 if (defined $uri->uid) {
36 push @{$lei->{lcat_todo}}, $lms->imap_oidhex($lei, $uri);
37 } elsif (defined(my $fid = $lms->fid_for($$uri))) {
38 push @{$lei->{lcat_todo}}, { fid => $fid };
40 lcat_folder($lei, $$uri);
44 sub lcat_nntp_uri ($$) {
46 my $mid = $uri->message; # already unescaped by URI::news
47 return "mid:$mid" if defined($mid);
48 my $lms = $lei->{-lms_ro} //= $lei->lms // return;
49 my ($ng, $beg, $end) = $uri->group;
51 lcat_folder($lei, $$uri, $beg, $end);
57 if ($x =~ m!\b(maildir:.+)!i) {
58 lcat_folder($lei, $1);
59 '""'; # blank query, using {lcat_todo}
60 } elsif ($x =~ m!\b(([a-z]+)://\S+)!i) {
61 my ($u, $scheme) = ($1, $2);
62 $u =~ s/[\>\]\)\,\.\;]+\z//;
63 if ($scheme =~ m!\A(imaps?)\z!i) {
64 require PublicInbox::URIimap;
65 lcat_imap_uri($lei, PublicInbox::URIimap->new($u));
66 return '""'; # blank query, using {lcat_todo}
67 } elsif ($scheme =~ m!\A(?:nntps?|s?news)\z!i) {
68 require PublicInbox::URInntps;
69 $u = PublicInbox::URInntps->new($u);
70 return lcat_nntp_uri($lei, $u);
71 } # http, or something else:
76 if ($p =~ m!([^/]+\@[^/]+)!) { # common msgid pattern
77 $term = 'mid:'.uri_unescape($1);
79 # is it a URL which returns the full thread?
80 if ($u->scheme =~ /\Ahttps?/i &&
81 $p =~ m!/(?:T/?|t/?|t\.mbox\.gz|t\.atom)\b!) {
83 $lei->{mset_opt}->{threads} = 1;
85 } elsif ($u->scheme =~ /\Ahttps?/i &&
86 # some msgids don't have '@', see if it looks like
88 $p =~ m!/([^/]+)/(raw|t/?|T/?|
89 t\.mbox\.gz|t\.atom)\z!x) {
90 $lei->{mset_opt}->{threads} = 1 if $2 && $2 ne 'raw';
91 $term = 'mid:'.uri_unescape($1);
94 } elsif ($x =~ $MID_EXTRACT) { # <$MSGID>
96 } elsif ($x =~ /\b((?:m|mid):\S+)/) { # our own prefixes (and mairix)
98 } elsif ($x =~ /\bid:(\S+)/) { # notmuch convention
100 } elsif ($x =~ /\bblob:([0-9a-f]{7,})\b/) {
101 push @{$lei->{lcat_todo}}, $1; # cf. LeiToMail->wq_atexit_child
109 my ($lei, @argv) = @_;
110 my $strict = !$lei->{opt}->{stdin};
113 if (my $term = extract_1($lei, $x)) {
116 return $lei->fail(<<"");
117 could not extract Message-ID from $x
121 delete $lei->{-lms_ro};
122 @q ? join(' OR ', @q) : $lei->fail("no Message-ID in: @argv");
125 sub _stdin { # PublicInbox::InputPipe::consume callback for --stdin
126 my ($lei) = @_; # $_[1] = $rbuf
127 if (defined($_[1])) {
128 $_[1] eq '' and return eval {
129 $lei->fchdir or return;
130 my @argv = split(/\s+/, $lei->{mset_opt}->{qstr});
131 $lei->{mset_opt}->{qstr} = extract_all($lei, @argv)
135 $lei->{mset_opt}->{qstr} .= $_[1];
137 $lei->fail("error reading stdin: $!");
142 my ($lei, @argv) = @_;
143 my $lxs = $lei->lxs_prepare or return;
144 $lei->ale->refresh_externals($lxs, $lei);
146 my $opt = $lei->{opt};
147 my %mset_opt = map { $_ => $opt->{$_} } qw(threads limit offset);
148 $mset_opt{asc} = $opt->{'reverse'} ? 1 : 0;
149 $mset_opt{limit} //= 10000;
150 $opt->{sort} //= 'relevance';
151 $mset_opt{relevance} = 1;
152 $lei->{mset_opt} = \%mset_opt;
153 $opt->{'format'} //= 'text' unless defined($opt->{output});
154 if ($lei->{opt}->{stdin}) {
155 return $lei->fail(<<'') if @argv;
156 no args allowed on command-line with --stdin
158 require PublicInbox::InputPipe;
159 PublicInbox::InputPipe::consume($lei->{0}, \&_stdin, $lei);
162 $lei->{mset_opt}->{qstr} = extract_all($lei, @argv) or return;
167 my ($lei, @argv) = @_;
168 my $lms = $lei->lms or return;
169 my $match_cb = $lei->complete_url_prepare(\@argv);
170 map { $match_cb->($_) } $lms->folders;