]> Sergey Matveev's repositories - public-inbox.git/blob - lib/PublicInbox/LeiLcat.pm
lei_mail_sync: account for non-unique cases
[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_folder ($$$) {
15         my ($lei, $lms, $folder) = @_;
16         $lms //= $lei->lms or 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(0, "# unknown folder: $folder");
22         } else {
23                 for my $f (@$folders) {
24                         my $fid = $lms->fid_for($f);
25                         push @{$lei->{lcat_fid}}, $fid;
26                 }
27         }
28 }
29
30 sub lcat_imap_uri ($$) {
31         my ($lei, $uri) = @_;
32         my $lms = $lei->lms or return;
33         # cf. LeiXsearch->lcat_dump
34         if (defined $uri->uid) {
35                 my @oidhex = $lms->imap_oidhex($lei, $uri);
36                 push @{$lei->{lcat_blob}}, @oidhex;
37         } elsif (defined(my $fid = $lms->fid_for($$uri))) {
38                 push @{$lei->{lcat_fid}}, $fid;
39         } else {
40                 lcat_folder($lei, $lms, $$uri);
41         }
42 }
43
44 sub extract_1 ($$) {
45         my ($lei, $x) = @_;
46         if ($x =~ m!\b(imaps?://[^>]+)!i) {
47                 my $u = $1;
48                 require PublicInbox::URIimap;
49                 lcat_imap_uri($lei, PublicInbox::URIimap->new($u));
50                 '""'; # blank query, using {lcat_blob} or {lcat_fid}
51         } elsif ($x =~ m!\b(maildir:.+)!i) {
52                 lcat_folder($lei, undef, $1);
53                 '""'; # blank query, using {lcat_blob} or {lcat_fid}
54         } elsif ($x =~ m!\b([a-z]+?://\S+)!i) {
55                 my $u = $1;
56                 $u =~ s/[\>\]\)\,\.\;]+\z//;
57                 require URI;
58                 $u = URI->new($u);
59                 my $p = $u->path;
60                 my $term;
61                 if ($p =~ m!([^/]+\@[^/]+)!) { # common msgid pattern
62                         $term = 'mid:'.uri_unescape($1);
63
64                         # is it a URL which returns the full thread?
65                         if ($u->scheme =~ /\Ahttps?/i &&
66                                 $p =~ m!/(?:T/?|t/?|t\.mbox\.gz|t\.atom)\b!) {
67
68                                 $lei->{mset_opt}->{threads} = 1;
69                         }
70                 } elsif ($u->scheme =~ /\Ahttps?/i &&
71                                 # some msgids don't have '@', see if it looks like
72                                 # a public-inbox URL:
73                                 $p =~ m!/([^/]+)/(raw|t/?|T/?|
74                                         t\.mbox\.gz|t\.atom)\z!x) {
75                         $lei->{mset_opt}->{threads} = 1 if $2 && $2 ne 'raw';
76                         $term = 'mid:'.uri_unescape($1);
77                 }
78                 $term;
79         } elsif ($x =~ $MID_EXTRACT) { # <$MSGID>
80                 "mid:$1";
81         } elsif ($x =~ /\b((?:m|mid):\S+)/) { # our own prefixes (and mairix)
82                 $1;
83         } elsif ($x =~ /\bid:(\S+)/) { # notmuch convention
84                 "mid:$1";
85         } elsif ($x =~ /\bblob:([0-9a-f]{7,})\b/) {
86                 push @{$lei->{lcat_blob}}, $1; # cf. LeiToMail->wq_atexit_child
87                 '""'; # blank query
88         } else {
89                 undef;
90         }
91 }
92
93 sub extract_all {
94         my ($lei, @argv) = @_;
95         my $strict = !$lei->{opt}->{stdin};
96         my @q;
97         for my $x (@argv) {
98                 if (my $term = extract_1($lei,$x)) {
99                         push @q, $term;
100                 } elsif ($strict) {
101                         return $lei->fail(<<"");
102 could not extract Message-ID from $x
103
104                 }
105         }
106         @q ? join(' OR ', @q) : $lei->fail("no Message-ID in: @argv");
107 }
108
109 sub _stdin { # PublicInbox::InputPipe::consume callback for --stdin
110         my ($lei) = @_; # $_[1] = $rbuf
111         if (defined($_[1])) {
112                 $_[1] eq '' and return eval {
113                         $lei->fchdir or return;
114                         my @argv = split(/\s+/, $lei->{mset_opt}->{qstr});
115                         $lei->{mset_opt}->{qstr} = extract_all($lei, @argv)
116                                 or return;
117                         $lei->_start_query;
118                 };
119                 $lei->{mset_opt}->{qstr} .= $_[1];
120         } else {
121                 $lei->fail("error reading stdin: $!");
122         }
123 }
124
125 sub lei_lcat {
126         my ($lei, @argv) = @_;
127         my $lxs = $lei->lxs_prepare or return;
128         $lei->ale->refresh_externals($lxs, $lei);
129         $lei->_lei_store(1);
130         my $opt = $lei->{opt};
131         my %mset_opt = map { $_ => $opt->{$_} } qw(threads limit offset);
132         $mset_opt{asc} = $opt->{'reverse'} ? 1 : 0;
133         $mset_opt{limit} //= 10000;
134         $opt->{sort} //= 'relevance';
135         $mset_opt{relevance} = 1;
136         $lei->{mset_opt} = \%mset_opt;
137         $opt->{'format'} //= 'text' unless defined($opt->{output});
138         if ($lei->{opt}->{stdin}) {
139                 return $lei->fail(<<'') if @argv;
140 no args allowed on command-line with --stdin
141
142                 require PublicInbox::InputPipe;
143                 PublicInbox::InputPipe::consume($lei->{0}, \&_stdin, $lei);
144                 return;
145         }
146         $lei->{mset_opt}->{qstr} = extract_all($lei, @argv) or return;
147         $lei->_start_query;
148 }
149
150 sub _complete_lcat {
151         my ($lei, @argv) = @_;
152         my $lms = $lei->lms or return;
153         my $match_cb = $lei->complete_url_prepare(\@argv);
154         map { $match_cb->($_) } $lms->folders;
155 }
156
157 1;