File Coverage

blib/lib/App/WatchLater.pm
Criterion Covered Total %
statement 23 69 33.3
branch 0 24 0.0
condition 0 6 0.0
subroutine 8 16 50.0
pod 1 1 100.0
total 32 116 27.5


line stmt bran cond sub pod time code
1             package App::WatchLater;
2              
3 1     1   42841 use 5.016;
  1         3  
4 1     1   7 use strict;
  1         3  
  1         29  
5 1     1   6 use warnings;
  1         2  
  1         32  
6              
7 1     1   7 use Carp;
  1         2  
  1         81  
8 1     1   1103 use DBI;
  1         11927  
  1         49  
9 1     1   445 use Getopt::Long qw(:config auto_help gnu_getopt);
  1         8799  
  1         4  
10 1     1   457 use Pod::Usage;
  1         33963  
  1         108  
11              
12 1     1   291 use App::WatchLater::YouTube;
  1         4  
  1         560  
13              
14             =head1 NAME
15              
16             App::WatchLater - Manage your YouTube Watch Later videos
17              
18             =head1 VERSION
19              
20             Version 0.01
21              
22             =cut
23              
24             our $VERSION = '0.01';
25              
26              
27             =head1 SYNOPSIS
28              
29             exit App::WatchLater::main();
30              
31             =head1 DESCRIPTION
32              
33             Manages a Watch Later queue of YouTube videos, in case you're one of the kinds
34             of people whose Watch Later lists get too out of hand. Google has deprecated the
35             ability to access the B playlist via the YouTube Data API, which means we
36             have to go to a bit more effort.
37              
38             An API key is required to access the YouTube Data API. Alternatively, requests
39             may be authorized by providing an OAuth2 access token.
40              
41             =head1 SUBROUTINES/METHODS
42              
43             =cut
44              
45             sub _ensure_schema {
46 0     0     my $dbh = shift;
47 0 0         $dbh->do(<<'SQL') or die $dbh->errstr;
48             CREATE TABLE IF NOT EXISTS videos(
49             video_id TEXT PRIMARY KEY,
50             video_title TEXT,
51             channel_id TEXT,
52             channel_title TEXT,
53             watched INTEGER NOT NULL DEFAULT 0
54             );
55             SQL
56             }
57              
58             sub _add {
59 0     0     my ($dbh, $api, @video_ids) = @_;
60              
61 0           my $sth = $dbh->prepare_cached(<<'SQL');
62             INSERT OR REPLACE INTO videos
63             (video_id, video_title, channel_id, channel_title, watched)
64             VALUES (?, ?, ?, ?, 0);
65             SQL
66              
67 0           for my $vid (@video_ids) {
68 0           my $snippet = $api->get_video($vid);
69             $sth->execute($vid, $snippet->{title},
70 0           $snippet->{channelId}, $snippet->{channelTitle});
71             }
72             }
73              
74             sub _get_random_video {
75 0     0     my ($dbh) = @_;
76 0           my $sth = $dbh->prepare_cached(<<'SQL');
77             SELECT video_id, video_title, channel_id, channel_title FROM videos
78             WHERE NOT watched
79             ORDER BY RANDOM()
80             LIMIT 1;
81             SQL
82 0 0         $sth->execute or die $sth->errstr;
83 0 0         my $row = $sth->fetchrow_hashref or croak 'no videos';
84 0           $row->{video_id};
85             }
86              
87             sub _get_browser {
88 0 0   0     return $ENV{BROWSER} if exists $ENV{BROWSER};
89 0           for ($^O) {
90 0 0 0       if (/MSWin32/ || /cygwin/) {
91 0           return 'start';
92             }
93 0 0         if (/darwin/) {
94 0           return 'open';
95             }
96 0 0         if (/linux/) {
97 0           return 'xdg-open';
98             }
99 0           croak 'unsupported operating system';
100             }
101             }
102              
103             sub _open_video {
104 0     0     my ($vid) = @_;
105 0           my $browser = _get_browser();
106 0           my $url = "https://youtu.be/$vid";
107 0           system { $browser } $browser, $url;
  0            
108             }
109              
110             sub _mark_watched {
111 0     0     my ($dbh, $vid) = @_;
112 0           my $sth = $dbh->prepare(<<'SQL');
113             UPDATE videos SET watched=1
114             WHERE video_id = ?;
115             SQL
116 0 0         $sth->execute($vid) or die $sth->errstr;
117             }
118              
119             sub _watch {
120 0     0     my ($dbh, $api, @video_ids) = @_;
121              
122 0 0         if (!@video_ids) {
123 0           push @video_ids, _get_random_video($dbh);
124             }
125              
126 0           for my $vid (@video_ids) {
127 0           _open_video($vid);
128 0           _mark_watched($dbh, $vid);
129             }
130             }
131              
132             =head2 main
133              
134             main();
135              
136             C runs the watch-later command line interface. It reads arguments
137             directly from C<@ARGV> using L.
138              
139             =cut
140              
141             # TODO a better module interface to main()
142             sub main {
143 0     0 1   my $dbpath = "$ENV{HOME}/.watch-later.db";
144 0           my $add = 0;
145 0           my $watch = 0;
146              
147 0 0         GetOptions(
148             'db-path|d=s' => \$dbpath,
149             'add|a' => \$add,
150             'watch|w' => \$watch,
151             ) or pod2usage(2);
152              
153 0 0 0       croak "Add and Watch modes both specified" if $add && $watch;
154              
155 0 0         my $handler = $watch ? \&_watch : \&_add;
156              
157 0           my $dbh = DBI->connect("dbi:SQLite:dbname=$dbpath");
158 0           _ensure_schema($dbh);
159              
160             my $api = App::WatchLater::YouTube->new(
161             api_key => $ENV{YT_API_KEY},
162             access_token => $ENV{YT_ACCESS_TOKEN},
163 0           );
164              
165 0           $handler->($dbh, $api, map { find_video_id($_) } @ARGV);
  0            
166             }
167              
168             =head1 AUTHOR
169              
170             Aaron L. Zeng, C<< >>
171              
172             =head1 BUGS
173              
174             Please report any bugs or feature requests to C, or through
175             the web interface at L. I will be notified, and then you'll
176             automatically be notified of progress on your bug as I make changes.
177              
178              
179              
180              
181             =head1 SUPPORT
182              
183             You can find documentation for this module with the perldoc command.
184              
185             perldoc App::WatchLater
186              
187              
188             You can also look for information at:
189              
190             =over 4
191              
192             =item * RT: CPAN's request tracker (report bugs here)
193              
194             L
195              
196             =item * AnnoCPAN: Annotated CPAN documentation
197              
198             L
199              
200             =item * CPAN Ratings
201              
202             L
203              
204             =item * Search CPAN
205              
206             L
207              
208             =back
209              
210              
211             =head1 ACKNOWLEDGEMENTS
212              
213              
214             =head1 LICENSE AND COPYRIGHT
215              
216             Copyright 2017 Aaron L. Zeng.
217              
218             This program is distributed under the MIT (X11) License:
219             L
220              
221             Permission is hereby granted, free of charge, to any person
222             obtaining a copy of this software and associated documentation
223             files (the "Software"), to deal in the Software without
224             restriction, including without limitation the rights to use,
225             copy, modify, merge, publish, distribute, sublicense, and/or sell
226             copies of the Software, and to permit persons to whom the
227             Software is furnished to do so, subject to the following
228             conditions:
229              
230             The above copyright notice and this permission notice shall be
231             included in all copies or substantial portions of the Software.
232              
233             THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
234             EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
235             OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
236             NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
237             HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
238             WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
239             FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
240             OTHER DEALINGS IN THE SOFTWARE.
241              
242              
243             =cut
244              
245             1; # End of App::WatchLater