Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

unable to login with api, no returns #128

Open
KBretz77 opened this issue Feb 19, 2023 · 28 comments
Open

unable to login with api, no returns #128

KBretz77 opened this issue Feb 19, 2023 · 28 comments

Comments

@KBretz77
Copy link

image

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)

@zener82
Copy link

zener82 commented Feb 20, 2023

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...

@igorskh
Copy link
Contributor

igorskh commented Feb 20, 2023

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.

@JASchilz
Copy link
Contributor

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):

image

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.

@flyinggoatman
Copy link

flyinggoatman commented Feb 20, 2023

That really sucks!

I'm having the same issue.

@sphanley
Copy link

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.

@igorskh
Copy link
Contributor

igorskh commented Feb 20, 2023

JWT authentication still works.

@sphanley
Copy link

JWT authentication still works.

Is there a way to acquire a token other than manually extracting one from a browser session, though?

@igorskh
Copy link
Contributor

igorskh commented Feb 20, 2023

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.

@KBretz77
Copy link
Author

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.

@flyinggoatman
Copy link

JWT authentication still works.

How do you use this method?

@JASchilz
Copy link
Contributor

@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. :)

@KBretz77
Copy link
Author

That's a good point, thanks @JASchilz

@KBretz77 KBretz77 reopened this Feb 20, 2023
@marvinscham
Copy link

@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);

@AJRepo
Copy link

AJRepo commented Mar 5, 2023

The solution by @marvinscham worked for me. Since jwt=None by default - I don't see why L100 is there.

@flyinggoatman
Copy link

Has he committed the solution yet?

@aandriella
Copy link

aandriella commented May 23, 2023

@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 get "Login failed".

@flyinggoatman
Copy link

flyinggoatman commented May 23, 2023 via email

@Vag-Soft
Copy link

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 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 get "Login failed".

@aandriella
Copy link

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?

@TiloGit
Copy link

TiloGit commented Jul 17, 2023

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.

imvickykumar999 added a commit to imvickykumar999/Duolingo that referenced this issue Jul 19, 2023
@tier61wro
Copy link

tier61wro commented Jul 19, 2023

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

@0xPorkchops
Copy link

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

@flyinggoatman
Copy link

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...

@filipre
Copy link

filipre commented Aug 27, 2023

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.

@mbrookes
Copy link

mbrookes commented Oct 6, 2023

@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 #128 (comment) with @flyinggoatman's instantiation #128 (comment) though, which is good enough for my personal use.

@dlo
Copy link

dlo commented Oct 28, 2023

To make this super straightforward, here's the full fix based on @mbrookes's recommendation, which worked for me (thank you!):

  1. Log into Duolingo web.

  2. Open the console and paste in document.cookie.match(new RegExp('(^| )jwt_token=([^;]+)'))[0].slice(11); to retrieve your JWT.

  3. 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')

@ExMacro
Copy link

ExMacro commented Mar 8, 2024

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?

@ExMacro
Copy link

ExMacro commented Mar 8, 2024

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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests