Created
June 18, 2019 21:59
-
-
Save JohnMertz/c869402cb8f54d14a3c1f8a0a54268af to your computer and use it in GitHub Desktop.
Duolingo API Script
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/perl | |
use strict; | |
use warnings; | |
use WWW::Mechanize; | |
use JSON::Any; | |
use HTTP::CookieJar::LWP; | |
my $user = 'username'; | |
my $pass = 'password'; | |
sub new { | |
my %params = @_; | |
if (!defined $params{user} || !defined $params{pass}) { | |
die "'new' requires your 'user' and 'pass' key values\n"; | |
} | |
my $cookie_jar = HTTP::CookieJar::LWP->new(); | |
if (!defined $params{mech}) { | |
$params{mech} = WWW::Mechanize->new(autocheck => 1, cookie_jar => $cookie_jar, agent => 'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; MDDRJS; rv:11.0) like Gecko'); | |
} | |
my $self = { username => $params{user}, password => $params{password}, mechanize => $params{mech}, base_url => 'https://www.duolingo.com' }; | |
bless $self; | |
$self->authenticate(); | |
return $self; | |
} | |
sub authenticate { | |
my $self = shift; | |
my $response = $self->{mechanize}->post($self->{base_url} . '/login', [ 'login' => $user, 'password' => $pass ]); | |
my $json = $response->decoded_content(); | |
my $content =JSON::XS::decode_json($json); | |
unless ( $content->{response} eq 'OK' ) { | |
die "Authentication Failed\n"; | |
} | |
$self->{user_id} = $content->{user_id}; | |
my $headers = $response->headers(); | |
$self->{jwt} = $headers->{jwt}; | |
} | |
sub response { | |
my $self = shift; | |
my %params = @_; | |
if (!defined $self->{jwt}) { | |
die "Session has not yet been authenticated. Must initialize with 'new'.\n"; | |
} | |
my $response = $self->{mechanize}->get($self->{base_url} . $params{resource} ); | |
my $json = $response->decoded_content(); | |
my $content = JSON::XS::decode_json($json); | |
return $content; | |
} | |
my $duo = new( user => $user, pass => $pass ); | |
# The regular API requires the tedious work of finding all activity | |
# timestamped for today, then adding up the xp to provide a total. | |
# I only want the streak length, goal, and today's xp, so I'm going | |
# to be lazy and use an older API that does the tally for me. | |
$duo->{base_url} = 'https://www.duolingo.com/2017-06-30'; | |
# The fetch line is the slightly different because the current API | |
# uses the 'username', while the old one uses the 'user_id'. Luckily, | |
# I stored the 'user_id' during authentication! | |
# my $data = $duo->response(resource => "/users/" . $self->{username}); | |
my $data = $duo->response(resource => "/users/" . $duo->{user_id}); | |
# Note: The data collected is structured different for each API | |
# version, so the following won't work for the current one. I also only | |
# print the values I care about. Dump the data structure to find which | |
# values you need: | |
# use Data::Dump 'dump'; | |
# print Data::Dump::dump($data); | |
# The current day's xp isn't entirely simple to fetch in the old API | |
# either. The only place that it is pre-calculated is in an array of | |
# global achievements: | |
my $xp; | |
my @achievements = @{$data->{_achievements}}; | |
foreach (@achievements) { | |
if ($_->{name} eq 'xp') { | |
$xp = $_->{count}; | |
last; | |
} | |
} | |
print JSON::XS::encode_json { goal_met => $data->{xpGoalMetToday}, streak => $data->{streakData}->{length}, xp => $xp . '/' . $data->{streakData}->{xpGoal} } ; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment