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, $lms, $folder) = @_;
16 $lms //= $lei->{lse}->lms // return;
17 my $folders = [ $folder];
18 my $err = $lms->arg2folder($lei, $folders);
19 $lei->qerr(@{$err->{qerr}}) if $err && $err->{qerr};
20 if ($err && $err->{fail}) {
21 $lei->child_error(1 << 8, "# unknown folder: $folder");
23 for my $f (@$folders) {
24 my $fid = $lms->fid_for($f);
25 push @{$lei->{lcat_fid}}, $fid;
30 sub lcat_imap_uri ($$) {
32 my $lms = $lei->{lse}->lms or return;
33 # cf. LeiXsearch->lcat_dump
34 if (defined $uri->uid) {
35 my $oidhex = $lms->imap_oid($lei, $uri);
36 if (ref(my $err = $oidhex)) { # art2folder error
37 $lei->qerr(@{$err->{qerr}}) if $err->{qerr};
39 push @{$lei->{lcat_blob}}, $oidhex;
40 } elsif (defined(my $fid = $lms->fid_for($$uri))) {
41 push @{$lei->{lcat_fid}}, $fid;
43 lcat_folder($lei, $lms, $$uri);
49 if ($x =~ m!\b(imaps?://[^>]+)!i) {
51 require PublicInbox::URIimap;
52 lcat_imap_uri($lei, PublicInbox::URIimap->new($u));
53 '""'; # blank query, using {lcat_blob} or {lcat_fid}
54 } elsif ($x =~ m!\b(maildir:.+)!i) {
55 lcat_folder($lei, undef, $1);
56 '""'; # blank query, using {lcat_blob} or {lcat_fid}
57 } elsif ($x =~ m!\b([a-z]+?://\S+)!i) {
59 $u =~ s/[\>\]\)\,\.\;]+\z//;
64 if ($p =~ m!([^/]+\@[^/]+)!) { # common msgid pattern
65 $term = 'mid:'.uri_unescape($1);
67 # is it a URL which returns the full thread?
68 if ($u->scheme =~ /\Ahttps?/i &&
69 $p =~ m!/(?:T/?|t/?|t\.mbox\.gz|t\.atom)\b!) {
71 $lei->{mset_opt}->{threads} = 1;
73 } elsif ($u->scheme =~ /\Ahttps?/i &&
74 # some msgids don't have '@', see if it looks like
76 $p =~ m!/([^/]+)/(raw|t/?|T/?|
77 t\.mbox\.gz|t\.atom)\z!x) {
78 $lei->{mset_opt}->{threads} = 1 if $2 && $2 ne 'raw';
79 $term = 'mid:'.uri_unescape($1);
82 } elsif ($x =~ $MID_EXTRACT) { # <$MSGID>
84 } elsif ($x =~ /\b((?:m|mid):\S+)/) { # our own prefixes (and mairix)
86 } elsif ($x =~ /\bid:(\S+)/) { # notmuch convention
88 } elsif ($x =~ /\bblob:([0-9a-f]{7,})\b/) {
89 push @{$lei->{lcat_blob}}, $1; # cf. LeiToMail->wq_atexit_child
97 my ($lei, @argv) = @_;
98 my $strict = !$lei->{opt}->{stdin};
101 if (my $term = extract_1($lei,$x)) {
104 return $lei->fail(<<"");
105 could not extract Message-ID from $x
109 @q ? join(' OR ', @q) : $lei->fail("no Message-ID in: @argv");
112 sub _stdin { # PublicInbox::InputPipe::consume callback for --stdin
113 my ($lei) = @_; # $_[1] = $rbuf
114 if (defined($_[1])) {
115 $_[1] eq '' and return eval {
116 $lei->fchdir or return;
117 my @argv = split(/\s+/, $lei->{mset_opt}->{qstr});
118 $lei->{mset_opt}->{qstr} = extract_all($lei, @argv)
122 $lei->{mset_opt}->{qstr} .= $_[1];
124 $lei->fail("error reading stdin: $!");
129 my ($lei, @argv) = @_;
130 my $lxs = $lei->lxs_prepare or return;
131 $lei->ale->refresh_externals($lxs);
132 my $sto = $lei->_lei_store(1);
133 $lei->{lse} = $sto->search;
134 my $opt = $lei->{opt};
135 my %mset_opt = map { $_ => $opt->{$_} } qw(threads limit offset);
136 $mset_opt{asc} = $opt->{'reverse'} ? 1 : 0;
137 $mset_opt{limit} //= 10000;
138 $opt->{sort} //= 'relevance';
139 $mset_opt{relevance} = 1;
140 $lei->{mset_opt} = \%mset_opt;
141 $opt->{'format'} //= 'text' unless defined($opt->{output});
142 if ($lei->{opt}->{stdin}) {
143 return $lei->fail(<<'') if @argv;
144 no args allowed on command-line with --stdin
146 require PublicInbox::InputPipe;
147 PublicInbox::InputPipe::consume($lei->{0}, \&_stdin, $lei);
150 $lei->{mset_opt}->{qstr} = extract_all($lei, @argv) or return;
155 my ($lei, @argv) = @_;
156 my $sto = $lei->_lei_store or return;
157 my $lms = $sto->search->lms or return;
158 my $match_cb = $lei->complete_url_prepare(\@argv);
159 map { $match_cb->($_) } $lms->folders;