App-WatchLater
view release on metacpan or search on metacpan
bin/yt-oauth view on Meta::CPAN
use autodie;
=head1 NAME
yt-oauth - A simple script for retrieving access and refresh tokens
=head1 VERSION
Version 0.03
=cut
our $VERSION = '0.03';
use Carp;
use Getopt::Long qw(:config auto_help gnu_getopt);
use HTTP::Tiny;
use JSON;
use Pod::Usage;
use Socket;
use App::WatchLater::Browser;
BEGIN {
my ($ok, $why) = HTTP::Tiny->can_ssl;
croak $why unless $ok;
}
my $CLIENT_ID;
my $CLIENT_SECRET;
my $http = HTTP::Tiny->new;
use constant OAUTH_URL_TEMPLATE => <<'URL' =~ s/\s//gr;
https://accounts.google.com/o/oauth2/v2/auth
?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fyoutube.readonly
&response_type=code
&redirect_uri=http://127.0.0.1:PORT
&client_id=CLIENT_ID
URL
# We ignore SIGCHLD, which has the effect of causing children to be
# automatically reaped instead of turning into zombie processes.
$SIG{CHLD} = 'IGNORE';
sub start_http_listener {
# setup socket to listen on arbitrary unused port
socket my $sock, PF_INET, SOCK_STREAM, 0;
my $sockaddr = pack_sockaddr_in 0, v127.0.0.1;
bind $sock, $sockaddr;
my ($port) = unpack_sockaddr_in getsockname $sock;
# setup a pipe and fork
if (open my $fh, '-|') {
# parent
return ($port, $fh);
} else {
# child
listen $sock, SOMAXCONN;
accept(my $client, $sock);
# response displayed to user after credentials are obtained
$client->autoflush(1);
print $client qq(HTTP/1.1 200 OK\r\n);
print $client qq(Content-Language: en-US\r\n);
print $client qq(Content-Type: text/html\r\n);
print $client qq(\r\n);
print $client qq(<html lang="en"><body>\r\n);
print $client qq(Successfully obtained OAuth credentials.\r\n);
print $client qq(This page may be closed.\r\n);
print $client qq(</body></html>);
# print credentials to pipe
my $response = <$client>;
print $response;
exit;
}
}
sub authenticate {
my ($port, $fh) = start_http_listener;
my $oauth_url = OAUTH_URL_TEMPLATE;
$oauth_url =~ s/CLIENT_ID/$CLIENT_ID/;
$oauth_url =~ s/PORT/$port/;
open_url($oauth_url);
my $response = <$fh>;
if ($response =~ m{GET /\?error=(\S+) HTTP/1.1}) {
croak "error authenticating: $1";
}
$response =~ m{GET /\?code=(\S+) HTTP/1.1} or confess "didn't authenticate";
$1, "http://127.0.0.1:$port";
}
sub get_tokens {
my ($auth_code, $redirect_uri) = @_;
my $resp = $http->post_form('https://www.googleapis.com/oauth2/v4/token', {
code => $auth_code,
client_id => $CLIENT_ID,
client_secret => $CLIENT_SECRET,
grant_type => 'authorization_code',
redirect_uri => $redirect_uri,
});
croak "$resp->{status} $resp->{reason}" unless $resp->{success};
my $json = $resp->{content};
my $obj = decode_json($json);
$obj->{access_token}, $obj->{refresh_token};
}
sub refresh_access {
my ($refresh_token) = @_;
my $resp = $http->post_form('https://www.googleapis.com/oauth2/v4/token', {
refresh_token => $refresh_token,
client_id => $CLIENT_ID,
client_secret => $CLIENT_SECRET,
grant_type => 'refresh_token',
});
croak "$resp->{status} $resp->{reason}" unless $resp->{success};
my $json = $resp->{content};
my $obj = decode_json($json);
$obj->{access_token};
}
my $access_token;
my $refresh_token;
my $show_version;
GetOptions(
'client-id|i=s' => \$CLIENT_ID,
'client-secret|s=s' => \$CLIENT_SECRET,
'refresh-token|r=s' => \$refresh_token,
'version|v' => \$show_version,
) or pod2usage(2);
if ($show_version) {
say 'yt-oauth ' . $VERSION;
exit;
}
$CLIENT_ID //= $ENV{YT_CLIENT_ID};
$CLIENT_SECRET //= $ENV{YT_CLIENT_SECRET};
pod2usage(2) unless defined $CLIENT_ID && defined $CLIENT_SECRET;
if (defined $refresh_token) {
$access_token = refresh_access($refresh_token);
} else {
my ($auth_code, $redirect_uri) = authenticate;
($access_token, $refresh_token) = get_tokens $auth_code, $redirect_uri;
}
say "access token: $access_token";
say "refresh token: $refresh_token";
=head1 SYNOPSIS
yt-oauth --client-id=ID --client-secret=SECRET [--refresh-token=TOKEN]
-i, --client-id set the client ID
-s, --client-secret set the client secret
-r, --refresh-token get a fresh access token using TOKEN
=head1 DESCRIPTION
Authenticate with Google APIs using OAuth2. Retrieves an access token and a
refresh token. Access token can be used to authorize API requests, and
furthermore provides access to user data. Access tokens will expire, so a new
one can be requested using the refresh token (B<--refresh-token>).
Client ID and client secret are mandatory, and may be provided on the command
line or by environment variable. A client ID and secret can be obtained from
Google's L<API Console|https://console.developers.google.com/apis/credentials>.
=head1 OPTIONS
=over 4
=item B<-i> I<id>, B<--client-id>=I<id>
=item B<-s> I<secret>, B<--client-secret>=I<secret>
=item B<-t> I<token>, B<--refresh-token>=I<token>
Obtain an access token using the provided refresh token instead of asking the
user to authorize. Refresh tokens may be stored and reused until they are
revoked by the user, while access tokens expire after a short amount of time.
=back
=head1 AUTHOR
Aaron L. Zeng <me@bcc32.com>
=cut
( run in 1.444 second using v1.01-cache-2.11-cpan-39bf76dae61 )