]> Sergey Matveev's repositories - public-inbox.git/blob - lib/PublicInbox/LeiLcat.pm
lei lcat: extract Message-IDs from URLs and show them
[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 URI;
13 use PublicInbox::MID qw($MID_EXTRACT);
14
15 sub lcat_redispatch {
16         my ($lei, $out, $op_p) = @_;
17         my $l = bless { %$lei }, ref($lei);
18         delete $l->{sock};
19         $l->{''} = $op_p; # daemon only
20         eval {
21                 $l->qerr("# updating $out");
22                 up1($l, $out);
23                 $l->qerr("# $out done");
24         };
25         $l->err($@) if $@;
26 }
27
28 sub extract_1 ($$) {
29         my ($lei, $x) = @_;
30         if ($x =~ m!\b([a-z]+?://\S+)!i) {
31                 my $u = $1;
32                 $u =~ s/[\>\]\)\,\.\;]+\z//;
33                 $u = URI->new($u);
34                 my $p = $u->path;
35                 my $term;
36                 if ($p =~ m!([^/]+\@[^/]+)!) { # common msgid pattern
37                         $term = 'mid:'.uri_unescape($1);
38
39                         # is it a URL which returns the full thread?
40                         if ($u->scheme =~ /\Ahttps?/i &&
41                                 $p =~ m!/(?:T/?|t/?|t\.mbox\.gz|t\.atom)\b!) {
42
43                                 $lei->{mset_opt}->{threads} = 1;
44                         }
45                 } elsif ($u->scheme =~ /\Ahttps?/i &&
46                                 # some msgids don't have '@', see if it looks like
47                                 # a public-inbox URL:
48                                 $p =~ m!/([^/]+)/(raw|t/?|T/?|
49                                         t\.mbox\.gz|t\.atom)\z!x) {
50                         $lei->{mset_opt}->{threads} = 1 if $2 && $2 ne 'raw';
51                         $term = 'mid:'.uri_unescape($1);
52                 }
53                 $term;
54         } elsif ($x =~ $MID_EXTRACT) { # <$MSGID>
55                 "mid:$1";
56         } elsif ($x =~ /\b((?:m|mid):\S+)/) { # our own prefixes (and mairix)
57                 $1;
58         } elsif ($x =~ /\bid:(\S+)/) { # notmuch convention
59                 "mid:$1";
60         } else {
61                 undef;
62         }
63 }
64
65 sub extract_all {
66         my ($lei, @argv) = @_;
67         my $strict = !$lei->{opt}->{stdin};
68         my @q;
69         for my $x (@argv) {
70                 if (my $term = extract_1($lei,$x)) {
71                         push @q, $term;
72                 } elsif ($strict) {
73                         return $lei->fail(<<"");
74 could not extract Message-ID from $x
75
76                 }
77         }
78         @q ? join(' OR ', @q) : $lei->fail("no Message-ID in: @argv");
79 }
80
81 sub _stdin { # PublicInbox::InputPipe::consume callback for --stdin
82         my ($lei) = @_; # $_[1] = $rbuf
83         if (defined($_[1])) {
84                 $_[1] eq '' and return eval {
85                         if (my $dfd = $lei->{3}) {
86                                 chdir($dfd) or return $lei->fail("fchdir: $!");
87                         }
88                         my @argv = split(/\s+/, $lei->{mset_opt}->{qstr});
89                         $lei->{mset_opt}->{qstr} = extract_all($lei, @argv)
90                                 or return;
91                         $lei->_start_query;
92                 };
93                 $lei->{mset_opt}->{qstr} .= $_[1];
94         } else {
95                 $lei->fail("error reading stdin: $!");
96         }
97 }
98
99 sub lei_lcat {
100         my ($lei, @argv) = @_;
101         my $lxs = $lei->lxs_prepare or return;
102         $lei->ale->refresh_externals($lxs);
103         my $sto = $lei->_lei_store(1);
104         $lei->{lse} = $sto->search;
105         my $opt = $lei->{opt};
106         my %mset_opt = map { $_ => $opt->{$_} } qw(threads limit offset);
107         $mset_opt{asc} = $opt->{'reverse'} ? 1 : 0;
108         $mset_opt{limit} //= 10000;
109         $opt->{sort} //= 'relevance';
110         $mset_opt{relevance} = 1;
111         $lei->{mset_opt} = \%mset_opt;
112         $opt->{'format'} //= 'mboxrd' unless defined($opt->{output});
113         if ($lei->{opt}->{stdin}) {
114                 return $lei->fail(<<'') if @argv;
115 no args allowed on command-line with --stdin
116
117                 require PublicInbox::InputPipe;
118                 PublicInbox::InputPipe::consume($lei->{0}, \&_stdin, $lei);
119                 return;
120         }
121         $lei->{mset_opt}->{qstr} = extract_all($lei, @argv) or return;
122         $lei->_start_query;
123 }
124
125 1;