Open KBretz77 opened 1 year ago
Hello, I experience the same troubles since few days. Was working fine before.
File "/usr/lib/python3.10/site-packages/duolingo.py", line 66, in __init__ self._login() File "/usr/lib/python3.10/site-packages/duolingo.py", line 105, in _login attempt = request.json() File "/usr/lib/python3.10/site-packages/requests/models.py", line 910, in json return complexjson.loads(self.text, **kwargs) File "/usr/lib/python3.10/json/__init__.py", line 346, in loads return _default_decoder.decode(s) File "/usr/lib/python3.10/json/decoder.py", line 337, in decode obj, end = self.raw_decode(s, idx=_w(s, 0).end()) File "/usr/lib/python3.10/json/decoder.py", line 355, in raw_decode raise JSONDecodeError("Expecting value", s, err.value) from None json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
From my understanding, it seems that the API cannot get any answer from the server with the API login function (or maybe bad formatted response? ). No change on my side with code that was working well before. One possibility is maybe a server protection, I was requesting data fro 3 accounts once an hour...
I think they've changed login procedure, I've just checked the login procedure on the web version, there are some additional fields in the login request. I didn't look closer, but I assume it's some sort of protection.
My experience lately is that this library is abandoned (which I'm not complaining about), so we're probably on our own in working through this issue.
@igorskh mentioned additional fields in the login request. I also see additional fields (although I'm not really sure what was there before):
And to me this also looks like additional authentication details to exclude unauthorized clients. Accessing Duolingo via this library has always kind of felt like an unauthorized hack, and now it feels to me like Duolingo might be closing that door.
That really sucks!
I'm having the same issue.
And to me this also looks like additional authentication details to exclude unauthorized clients. Accessing Duolingo via this library has always kind of felt like an unauthorized hack, and now it feels to me like Duolingo might be closing that door.
Yeah, if you look through the calls being made on the login page prior to that call to login?fields=
, you'll see that they've added recapcha authentication, and the value provided from recapcha is required to log in. It seems clear that this is overtly intended to prevent logins except through the official clients. Unfortunately, projects dependent on this library are likely dead unless Duolingo decides to provide an official API down the road.
JWT authentication still works.
JWT authentication still works.
Is there a way to acquire a token other than manually extracting one from a browser session, though?
JWT authentication still works.
Is there a way to acquire a token other than manually extracting one from a browser session, though?
No easy way that I know of. However, at the moment it's sufficient to get it only once, since they practically don't expire.
Thanks so much everyone! I'm glad it wasn't just me and that it's sourced from Duolingo. I agree that the API call did seem like low-key hacking, so it doesn't surprise me that they have closed that loophole. I'll just grab the XP progress and other details from my user page for now and submit a request to Duolingo for them to create their own API.
JWT authentication still works.
How do you use this method?
@KBretz77 you might consider re-opening the issue. Even if you're not hoping for a solution, leaving the issue open serves as a helpful notice for anyone trying to use the library who runs into this problem. :)
That's a good point, thanks @JASchilz
@flyinggoatman
I fixed this for my usecase by removing self.jwt = None
at https://github.com/KartikTalwar/Duolingo/blob/master/duolingo.py#L100, which then allowed me to instantiate like
lingo = duolingo.Duolingo(username='myUsername', jwt='myJWT')
You can grab your JWT by logging in on duolingo.com and then running this JavaScript, which will output the token into the console:
document.cookie.match(new RegExp('(^| )jwt_token=([^;]+)'))[0].slice(11);
The solution by @marvinscham worked for me. Since jwt=None by default - I don't see why L100 is there.
Has he committed the solution yet?
@flyinggoatman I fixed this for my usecase by removing
self.jwt = None
at https://github.com/KartikTalwar/Duolingo/blob/master/duolingo.py#L100, which then allowed me to instantiate likelingo = duolingo.Duolingo(username='myUsername', jwt='myJWT')
You can grab your JWT by logging in on duolingo.com and then running this JavaScript, which will output the token into the console:
document.cookie.match(new RegExp('(^| )jwt_token=([^;]+)'))[0].slice(11);
For me still does not work. I get "Login failed".
They’ve probably patched it again... Let me check my code.
From: Antonio @.> Sent: 23 May 2023 16:43 To: @.> Cc: @.>; @.> Subject: Re: [KartikTalwar/Duolingo] unable to login with api, no returns (Issue #128)
@flyinggoatmanhttps://github.com/flyinggoatman I fixed this for my usecase by removing self.jwt = None at https://github.com/KartikTalwar/Duolingo/blob/master/duolingo.py#L100, which then allowed me to instantiate like lingo = duolingo.Duolingo(username='myUsername', jwt='myJWT')
You can grab your JWT by logging in on duolingo.com and then running this JavaScript, which will output the token into the console: document.cookie.match(new RegExp('(^| )jwt_token=([^;]+)'))[0].slice(11);
For me still does not work. I got "Login failed".
— Reply to this email directly, view it on GitHubhttps://github.com/KartikTalwar/Duolingo/issues/128#issuecomment-1559698203, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AAZ2VUS2Q2IL244H4YSEJ6TXHTLLRANCNFSM6AAAAAAVAWDJ5E. You are receiving this because you were mentioned.Message ID: @.***>
I used this code to modify the Duolingo API to make JWT authorization work. It's still working for me to this day.
import inspect
source = inspect.getsource(duolingo)
new_source = source.replace('jwt=None', 'jwt')
new_source = source.replace('self.jwt = None', ' ')
exec(new_source, duolingo.__dict__)
@flyinggoatman I fixed this for my usecase by removing
self.jwt = None
at https://github.com/KartikTalwar/Duolingo/blob/master/duolingo.py#L100, which then allowed me to instantiate likelingo = duolingo.Duolingo(username='myUsername', jwt='myJWT')
You can grab your JWT by logging in on duolingo.com and then running this JavaScript, which will output the token into the console:document.cookie.match(new RegExp('(^| )jwt_token=([^;]+)'))[0].slice(11);
For me still does not work. I get "Login failed".
They’ve probably patched it again... Let me check my code. From: Antonio @.> Sent: 23 May 2023 16:43 To: @.> Cc: @.>; @.> Subject: Re: [KartikTalwar/Duolingo] unable to login with api, no returns (Issue #128) @flyinggoatmanhttps://github.com/flyinggoatman I fixed this for my usecase by removing self.jwt = None at https://github.com/KartikTalwar/Duolingo/blob/master/duolingo.py#L100, which then allowed me to instantiate like lingo = duolingo.Duolingo(username='myUsername', jwt='myJWT') You can grab your JWT by logging in on duolingo.com and then running this JavaScript, which will output the token into the console: document.cookie.match(new RegExp('(^| )jwt_token=([^;]+)'))[0].slice(11); For me still does not work. I got "Login failed". — Reply to this email directly, view it on GitHub<#128 (comment)>, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AAZ2VUS2Q2IL244H4YSEJ6TXHTLLRANCNFSM6AAAAAAVAWDJ5E. You are receiving this because you were mentioned.Message ID: @.***>
@flyinggoatman did you have the time to check if that is still working for you?
tested today and with the changes
replace('jwt=None', 'jwt')
replace('self.jwt = None', ' ')
and use jwt token worked.
here my main.py for the AWS Lambda function (username,pw and jwt token are Environment Variables of Lambda)
def item_already_equipped(lingo, item):
if item == 'streak_freeze':
return lingo.__dict__['user_data'].__dict__['tracking_properties']['num_item_streak_freeze'] > 0
if item == 'rupee_wager':
return lingo.__dict__['user_data'].__dict__['tracking_properties']['has_item_rupee_wager']
def main(a, b):
import duolingo, os
username = os.environ['usernames']
password = os.environ['passwords']
jwt = os.environ['jwt']
print("Test__Tilo__Here")
print("environment variable: " + os.environ['usernames'])
print("loaded jwt (first 10 char: " + jwt[:10])
print("loaded one: " + username)
try:
#lingo = duolingo.Duolingo(username, password)
lingo = duolingo.Duolingo(username=os.environ['usernames'], jwt=os.environ['jwt'])
except ValueError:
raise Exception("Username or login Invalid")
#here the test/report stuff
print("---InfoPart---Start---")
Mylanguages = lingo.get_languages()
print(username + " get_languages for " + str(Mylanguages))
streak_info = lingo.get_streak_info()
print(username +" get_streak_info for " + str(streak_info))
MyInfo = lingo.get_user_info()
print(username +" Info ID: " + str(MyInfo["id"]))
print(username +" Info fullname: " + str(MyInfo["fullname"]))
print(username +" Info location: " + str(MyInfo["location"]))
print(username +" Info contribution_points: " + str(MyInfo["contribution_points"]))
print(username +" Info created: " + str.strip(MyInfo["created"]))
print(username +" Info learning_language_string: " + str(MyInfo["learning_language_string"]))
print(username +" streak_freeze: " + str(lingo.__dict__['user_data'].__dict__['tracking_properties']['num_item_streak_freeze']))
print(username +" rupee_wager: " + str(lingo.__dict__['user_data'].__dict__['tracking_properties']['has_item_rupee_wager']))
user_data_resp = lingo.get_data_by_user_id()
print(username +" Info lingots: " + str(user_data_resp['lingots']))
print(username +" Info totalXp: " + str(user_data_resp['totalXp']))
print(username +" Info monthlyXp: " + str(user_data_resp['monthlyXp']))
print(username +" Info weeklyXp: " + str(user_data_resp['weeklyXp']))
print(username +" Info gems: " + str(user_data_resp['gems']))
print(username +" Info currentCourse.crowns: " + str(user_data_resp['currentCourse']['crowns']))
print("---InfoPart---End---")
#new buy stuff 2020-08-31
stuff_to_purchase = ['streak_freeze', 'rupee_wager']
for item in stuff_to_purchase:
if(item_already_equipped(lingo, item)):
print("Item "+ item + " already equipped! Skipping...")
continue
try:
print("Trying to Buy " + item + " for " + username)
lingo.buy_item(item, 'es')
print("Bought " + item + " for " + username)
except duolingo.AlreadyHaveStoreItemException: # no longer triggered AFAIK
print("Item Already Equipped")
except Exception:
raise ValueError("Unable to buy " + item)
cheers.
Hello everyone,
Like you, I was quite disheartened when the Duolingo API library stopped working and was giving an authorization error. I am a volunteer. I use Duolingo to teach languages to refugee children who have fled from the war in Ukraine. Therefore, discovering that the library was broken was quite distressing.
By examining how Duolingo operates through the browser console, I managed to create a minor fix, and now everything is running smoothly on my end. Essentially, I changed the login_url and the parameters passed in the request to obtain the authorization token.
I have uploaded a version with the fix in this repository: https://github.com/tier61wro/Duolingo. Perhaps some of you will find this useful!
I'm not confident that my fix will work forever, so I don't think it makes sense to make a merge request into the main repository at this point.
Best regards, Alex
I've found that there is still an unauthenticated endpoint that allows you to retrieve user data. Perhaps there are more methods that can be called on this endpoint, but I am only interested in some public user data. https://www.duolingo.com/2017-06-30/users?username=USERNAME-HERE
I've found that there is still an unauthenticated endpoint that allows you to retrieve user data. Perhaps there are more methods that can be called on this endpoint, but I am only interested in some public user data. https://www.duolingo.com/2017-06-30/users?username=USERNAME-HERE
Fuck that's dangurous. My email is just... there...
Fuck that's dangurous. My email is just... there...
I played around with it and it seems like the endpoint returns different data for your own username depending on whether you are logged in or not. They use cookies to authenticate the API request. So your email and phone number are not really exposed, even though it looks like it.
@tier61wro Thanks for sharing it, but unfortunately your fix doesn't work for me:
Traceback (most recent call last):
File "/Users/matt/Projects/duolingo/test.py", line 3, in <module>
lingo = duolingo.Duolingo('username', 'password')
File "/Users/matt/Projects/duolingo/duolingo.py", line 66, in __init__
self._login()
File "/Users/matt/Projects/duolingo/duolingo.py", line 108, in _login
self.jwt = request.headers['jwt']
File "/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/site-packages/requests/structures.py", line 52, in __getitem__
return self._store[key.lower()][1]
KeyError: 'jwt'
I was able to use a combination of @Vag-Soft's patch https://github.com/KartikTalwar/Duolingo/issues/128#issuecomment-1559963434 with @flyinggoatman's instantiation https://github.com/KartikTalwar/Duolingo/issues/128#issuecomment-1559710952 though, which is good enough for my personal use.
To make this super straightforward, here's the full fix based on @mbrookes's recommendation, which worked for me (thank you!):
document.cookie.match(new RegExp('(^| )jwt_token=([^;]+)'))[0].slice(11);
to retrieve your JWT. Use the following code to use the API:
import duolingo
import inspect
source = inspect.getsource(duolingo)
new_source = source.replace('jwt=None', 'jwt')
new_source = source.replace('self.jwt = None', ' ')
exec(new_source, duolingo.__dict__)
lingo = duolingo.Duolingo('YOUR_USERNAME', jwt='YOUR_JWT_FROM_ABOVE')
Hi! Nice work with this fix! I managed to download and started the UI setup. By running the code
document.cookie.match(new RegExp('(^| )jwt_token=([^;]+)'))[0].slice(11);
does return a long string of characters but unfortunately the integration doesn't login with that.
Any ideas?
Hi! Nice work with this fix! I managed to download and started the UI setup. By running the code
document.cookie.match(new RegExp('(^| )jwt_token=([^;]+)'))[0].slice(11);
does return a long string of characters but unfortunately the integration doesn't login with that.Any ideas?
I will answer to myself. The "username" in the login is not the login email address but the user profile -> user name just above "joined September 2022" etc. info in the Duolingo web site. Now working!
Ever since February 16th, I've been unable to access the API, even after verifying my username/password. I keep getting this exception:
Traceback (most recent call last): File "[USER SCRIPT] ", line 10, in
lingo = duolingo.Duolingo('[USER], [PW]')
File "[USER ENV] \lib\site-packages\duolingo.py", line 66, in init
self._login()
File "[USER ENV] \lib\site-packages\duolingo.py", line 105, in _login
attempt = request.json()
File "[USER ENV] \lib\site-packages\requests\models.py", line 975, in json
raise RequestsJSONDecodeError(e.msg, e.doc, e.pos)
requests.exceptions.JSONDecodeError: Expecting value: line 1 column 1 (char 0)