File Coverage

blib/lib/App/WatchLater/YouTube.pm
Criterion Covered Total %
statement 48 49 97.9
branch 11 16 68.7
condition 3 9 33.3
subroutine 12 12 100.0
pod 4 4 100.0
total 78 90 86.6


line stmt bran cond sub pod time code
1             package App::WatchLater::YouTube;
2              
3 3     3   1252 use 5.016;
  3         14  
4 3     3   17 use strict;
  3         5  
  3         71  
5 3     3   16 use warnings;
  3         7  
  3         292  
6              
7             =head1 NAME
8              
9             App::WatchLater::YouTube - The YouTube Data API
10              
11             =head1 VERSION
12              
13             Version 0.02
14              
15             =cut
16              
17             our $VERSION = '0.02';
18              
19              
20             =head1 SYNOPSIS
21              
22             This is a simple module for making requests to the YouTube Data API.
23             Authorization is required, and can be obtained by registering for an API key
24             from the Google Developer L
25             Console|https://console.developers.google.com/apis/credentials>. Alternatively,
26             obtain user authorization through OAuth2 using the yt-oauth(1) script.
27              
28             my $api = App::WatchLater::YouTube->new(
29             access_token => ...,
30             api_key => ...,
31             );
32              
33             # returns the body of the HTTP response as a string
34             my $body = $api->request('GET', '/videos', {
35             id => 'Ks-_Mh1QhMc',
36             part => 'snippet'
37             });
38             ...
39              
40             =head1 EXPORT
41              
42             =over 4
43              
44             =item *
45              
46             C - exported by default.
47              
48             =back
49              
50             =cut
51              
52             BEGIN {
53 3     3   21 require Exporter;
54 3         40 our @ISA = qw(Exporter);
55 3         9 our @EXPORT = qw(find_video_id);
56 3         450 our @EXPORT_OK = qw(find_video_id);
57             }
58              
59             =head1 SUBROUTINES/METHODS
60              
61             =head2 find_video_id
62              
63             my $video_id = find_video_id($url);
64              
65             Find a YouTube video ID from a YouTube watch URL. Also accepts I
66             shortened URLs and literal video IDs.
67              
68             =cut
69              
70             my $regex = qr/[a-z0-9_-]+/i;
71              
72             sub find_video_id {
73 4     4 1 823 local $_ = shift;
74 4 100       71 return $1 if m{youtube\.com/watch.*\bv=($regex)};
75 3 100       43 return $1 if m{youtu.be/($regex)};
76 2 100       39 return $1 if m{^($regex)$};
77 1         13 die "'$_' is not a valid video ID";
78             }
79              
80             =head2 new
81              
82             my $api = App::WatchLater::YouTube->new(%opts)
83              
84             This constructor returns a new API object. Attributes include:
85              
86             =over 4
87              
88             =item *
89              
90             C - an instance of HTTP::Tiny. If none is provided, a default new instance
91             is used.
92              
93             =item *
94              
95             C - an API key.
96              
97             =item *
98              
99             C - an OAuth2 access token.
100              
101             =back
102              
103             At least one of C and C must be provided. If both are
104             provided, C is used for authorization.
105              
106             =cut
107              
108 3     3   22 use Carp;
  3         6  
  3         249  
109 3     3   1477 use HTTP::Tiny;
  3         102949  
  3         103  
110 3     3   788 use JSON;
  3         12075  
  3         17  
111              
112             BEGIN {
113 3     3   390 my ($ok, $why) = HTTP::Tiny->can_ssl;
114 3 50       104715 croak $why unless $ok;
115             }
116              
117             sub new {
118 1     1 1 1246 my ($class, %opts) = @_;
119              
120 1   33     8 my $http = $opts{http} // HTTP::Tiny->new;
121 1         2 my $key = $opts{api_key};
122 1         2 my $token = $opts{access_token};
123              
124 1 50 33     2 defined $key || defined $token
125             or croak "no API key or access token, aborting";
126              
127 1         5 bless {
128             http => $http,
129             key => $key,
130             token => $token,
131             } => $class;
132             }
133              
134             =head2 request
135              
136             my $body = $api->request($method, $endpoint, %params);
137              
138             Send a request to the specified API endpoint using the given HTTP method. Query
139             parameters may be specified in C<%params>. Croaks if the request fails.
140              
141             =cut
142              
143             sub request {
144 1     1 1 4 my ($self, $method, $endpoint, %params) = @_;
145 1         3 my $url = 'https://www.googleapis.com/youtube/v3' . $endpoint;
146              
147 1         8 my %headers;
148              
149 1 50       6 if (defined $self->{token}) {
150 0         0 $headers{Authorization} = 'Bearer ' . $self->{token};
151             } else {
152 1   33     6 $params{key} ||= $self->{key};
153             }
154              
155 1         8 my $query = $self->{http}->www_form_urlencode(\%params);
156 1         120 my $response = $self->{http}->request($method, "$url?$query", {
157             headers => \%headers,
158             });
159 1 50       637 croak "$response->{status} $response->{reason}" unless $response->{success};
160 1         4 $response->{content};
161             }
162              
163             =head2 get_video
164              
165             my \%snippet = $api->get_video($video_id);
166              
167             Retrieves a YouTube video resource, including the snippet, for the video given
168             by C<$video_id>. Croaks if no such video is found.
169              
170             =cut
171              
172             sub get_video {
173 1     1 1 305 my ($self, $video_id) = @_;
174 1         4 my $json = $self->request(
175             'GET', '/videos',
176             id => $video_id,
177             part => 'snippet',
178             );
179 1         43 my $obj = decode_json($json);
180 1 50       5 my $item = $obj->{items}[0] or croak "no video with id $video_id";
181 1         5 $item->{snippet};
182             }
183              
184             =head1 AUTHOR
185              
186             Aaron L. Zeng, C<< >>
187              
188             =head1 BUGS
189              
190             Please report any bugs or feature requests to C, or through
191             the web interface at L. I will be notified, and then you'll
192             automatically be notified of progress on your bug as I make changes.
193              
194              
195              
196              
197             =head1 SUPPORT
198              
199             You can find documentation for this module with the perldoc command.
200              
201             perldoc App::WatchLater::YouTube
202              
203              
204             You can also look for information at:
205              
206             =over 4
207              
208             =item * RT: CPAN's request tracker (report bugs here)
209              
210             L
211              
212             =item * AnnoCPAN: Annotated CPAN documentation
213              
214             L
215              
216             =item * CPAN Ratings
217              
218             L
219              
220             =item * Search CPAN
221              
222             L
223              
224             =back
225              
226              
227             =head1 ACKNOWLEDGEMENTS
228              
229              
230             =head1 LICENSE AND COPYRIGHT
231              
232             Copyright 2017 Aaron L. Zeng.
233              
234             This program is distributed under the MIT (X11) License:
235             L
236              
237             Permission is hereby granted, free of charge, to any person
238             obtaining a copy of this software and associated documentation
239             files (the "Software"), to deal in the Software without
240             restriction, including without limitation the rights to use,
241             copy, modify, merge, publish, distribute, sublicense, and/or sell
242             copies of the Software, and to permit persons to whom the
243             Software is furnished to do so, subject to the following
244             conditions:
245              
246             The above copyright notice and this permission notice shall be
247             included in all copies or substantial portions of the Software.
248              
249             THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
250             EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
251             OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
252             NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
253             HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
254             WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
255             FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
256             OTHER DEALINGS IN THE SOFTWARE.
257              
258              
259             =cut
260              
261             1; # End of App::WatchLater::YouTube