]> Sergey Matveev's repositories - public-inbox.git/blob - lib/PublicInbox/LeiLcat.pm
lei: handle a single IMAP message in most places
[public-inbox.git] / lib / PublicInbox / LeiLcat.pm
1 # Copyright (C) 2021 all contributors <meta@public-inbox.org>
2 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
3
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;
8 use strict;
9 use v5.10.1;
10 use PublicInbox::LeiViewText;
11 use URI::Escape qw(uri_unescape);
12 use PublicInbox::MID qw($MID_EXTRACT);
13
14 sub lcat_imap_uid_uri ($$) {
15         my ($lei, $uid_uri) = @_;
16         my $lms = $lei->{lse}->lms or return;
17         my $oidhex = $lms->imap_oid($lei, $uid_uri);
18         if (ref(my $err = $oidhex)) { # art2folder error
19                 $lei->qerr(@{$err->{qerr}}) if $err->{qerr};
20         }
21         push @{$lei->{lcat_blob}}, $oidhex; # cf. LeiToMail->wq_atexit_child
22 }
23
24 sub extract_1 ($$) {
25         my ($lei, $x) = @_;
26         if ($x =~ m!\b(imaps?://[^>]+)!i) {
27                 my $u = $1;
28                 require PublicInbox::URIimap;
29                 $u = PublicInbox::URIimap->new($u);
30                 defined($u->uid) ? lcat_imap_uid_uri($lei, $u) :
31                                 $lei->child_error(1 << 8, "# no UID= in $u");
32                 '""'; # blank query, using {lcat_blob}
33         } elsif ($x =~ m!\b([a-z]+?://\S+)!i) {
34                 my $u = $1;
35                 $u =~ s/[\>\]\)\,\.\;]+\z//;
36                 require URI;
37                 $u = URI->new($u);
38                 my $p = $u->path;
39                 my $term;
40                 if ($p =~ m!([^/]+\@[^/]+)!) { # common msgid pattern
41                         $term = 'mid:'.uri_unescape($1);
42
43                         # is it a URL which returns the full thread?
44                         if ($u->scheme =~ /\Ahttps?/i &&
45                                 $p =~ m!/(?:T/?|t/?|t\.mbox\.gz|t\.atom)\b!) {
46
47                                 $lei->{mset_opt}->{threads} = 1;
48                         }
49                 } elsif ($u->scheme =~ /\Ahttps?/i &&
50                                 # some msgids don't have '@', see if it looks like
51                                 # a public-inbox URL:
52                                 $p =~ m!/([^/]+)/(raw|t/?|T/?|
53                                         t\.mbox\.gz|t\.atom)\z!x) {
54                         $lei->{mset_opt}->{threads} = 1 if $2 && $2 ne 'raw';
55                         $term = 'mid:'.uri_unescape($1);
56                 }
57                 $term;
58         } elsif ($x =~ $MID_EXTRACT) { # <$MSGID>
59                 "mid:$1";
60         } elsif ($x =~ /\b((?:m|mid):\S+)/) { # our own prefixes (and mairix)
61                 $1;
62         } elsif ($x =~ /\bid:(\S+)/) { # notmuch convention
63                 "mid:$1";
64         } elsif ($x =~ /\bblob:([0-9a-f]{7,})\b/) {
65                 push @{$lei->{lcat_blob}}, $1; # cf. LeiToMail->wq_atexit_child
66                 '""'; # blank query
67         } else {
68                 undef;
69         }
70 }
71
72 sub extract_all {
73         my ($lei, @argv) = @_;
74         my $strict = !$lei->{opt}->{stdin};
75         my @q;
76         for my $x (@argv) {
77                 if (my $term = extract_1($lei,$x)) {
78                         push @q, $term;
79                 } elsif ($strict) {
80                         return $lei->fail(<<"");
81 could not extract Message-ID from $x
82
83                 }
84         }
85         @q ? join(' OR ', @q) : $lei->fail("no Message-ID in: @argv");
86 }
87
88 sub _stdin { # PublicInbox::InputPipe::consume callback for --stdin
89         my ($lei) = @_; # $_[1] = $rbuf
90         if (defined($_[1])) {
91                 $_[1] eq '' and return eval {
92                         if (my $dfd = $lei->{3}) {
93                                 chdir($dfd) or return $lei->fail("fchdir: $!");
94                         }
95                         my @argv = split(/\s+/, $lei->{mset_opt}->{qstr});
96                         $lei->{mset_opt}->{qstr} = extract_all($lei, @argv)
97                                 or return;
98                         $lei->_start_query;
99                 };
100                 $lei->{mset_opt}->{qstr} .= $_[1];
101         } else {
102                 $lei->fail("error reading stdin: $!");
103         }
104 }
105
106 sub lei_lcat {
107         my ($lei, @argv) = @_;
108         my $lxs = $lei->lxs_prepare or return;
109         $lei->ale->refresh_externals($lxs);
110         my $sto = $lei->_lei_store(1);
111         $lei->{lse} = $sto->search;
112         my $opt = $lei->{opt};
113         my %mset_opt = map { $_ => $opt->{$_} } qw(threads limit offset);
114         $mset_opt{asc} = $opt->{'reverse'} ? 1 : 0;
115         $mset_opt{limit} //= 10000;
116         $opt->{sort} //= 'relevance';
117         $mset_opt{relevance} = 1;
118         $lei->{mset_opt} = \%mset_opt;
119         $opt->{'format'} //= 'text' unless defined($opt->{output});
120         if ($lei->{opt}->{stdin}) {
121                 return $lei->fail(<<'') if @argv;
122 no args allowed on command-line with --stdin
123
124                 require PublicInbox::InputPipe;
125                 PublicInbox::InputPipe::consume($lei->{0}, \&_stdin, $lei);
126                 return;
127         }
128         $lei->{mset_opt}->{qstr} = extract_all($lei, @argv) or return;
129         $lei->_start_query;
130 }
131
132 1;