GithubHelp home page GithubHelp logo

Comments (28)

dlo avatar dlo commented on July 17, 2024 19

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

from duolingo.

marvinscham avatar marvinscham commented on July 17, 2024 15

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

from duolingo.

JASchilz avatar JASchilz commented on July 17, 2024 3

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

from duolingo.

Vag-Soft avatar Vag-Soft commented on July 17, 2024 3

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

from duolingo.

filipre avatar filipre commented on July 17, 2024 3

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.

from duolingo.

JASchilz avatar JASchilz commented on July 17, 2024 2

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.

from duolingo.

flyinggoatman avatar flyinggoatman commented on July 17, 2024 2

JWT authentication still works.

How do you use this method?

from duolingo.

flyinggoatman avatar flyinggoatman commented on July 17, 2024 2

Has he committed the solution yet?

from duolingo.

TiloGit avatar TiloGit commented on July 17, 2024 2

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.

from duolingo.

mbrookes avatar mbrookes commented on July 17, 2024 2

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

from duolingo.

KBretz77 avatar KBretz77 commented on July 17, 2024 1

That's a good point, thanks @JASchilz

from duolingo.

flyinggoatman avatar flyinggoatman commented on July 17, 2024 1

from duolingo.

tier61wro avatar tier61wro commented on July 17, 2024 1

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

from duolingo.

zener82 avatar zener82 commented on July 17, 2024

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

from duolingo.

igorskh avatar igorskh commented on July 17, 2024

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.

from duolingo.

flyinggoatman avatar flyinggoatman commented on July 17, 2024

That really sucks!

I'm having the same issue.

from duolingo.

sphanley avatar sphanley commented on July 17, 2024

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.

from duolingo.

igorskh avatar igorskh commented on July 17, 2024

JWT authentication still works.

from duolingo.

sphanley avatar sphanley commented on July 17, 2024

JWT authentication still works.

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

from duolingo.

igorskh avatar igorskh commented on July 17, 2024

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.

from duolingo.

KBretz77 avatar KBretz77 commented on July 17, 2024

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.

from duolingo.

AJRepo avatar AJRepo commented on July 17, 2024

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

from duolingo.

aandriella avatar aandriella commented on July 17, 2024

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

from duolingo.

aandriella avatar aandriella commented on July 17, 2024

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?

from duolingo.

0xPorkchops avatar 0xPorkchops commented on July 17, 2024

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

from duolingo.

flyinggoatman avatar flyinggoatman commented on July 17, 2024

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

from duolingo.

ExMacro avatar ExMacro commented on July 17, 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?

from duolingo.

ExMacro avatar ExMacro commented on July 17, 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!

from duolingo.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.