GithubHelp home page GithubHelp logo

duolingo's Introduction

Duolingo API for Python

Build Status Coverage Status PyPI version fury.io

Unofficial Duolingo API Written in Python. This is mostly a collection of functions that give you common data directly from the API resource dictionary. More methods to come.

TODO
  • Integrate authenticated data

Installation

$ pip install duolingo-api

Usage

import duolingo
lingo  = duolingo.Duolingo('kartik', 'my password')

Note: You are now required to provide a password to get any data from the Duolingo API

Documentation

Account Information
Switch account being read
Language Information

Get User Information

lingo.get_user_info()

Returns a dictionary containing various information on the user, including their avatar, user ID, location, current language, and more.

# Sample Request
lingo  = duolingo.Duolingo('kartik', '...')
print(lingo.get_user_info())

# Sample Response
{
    'admin': False,
    'avatar': 'https://s3.amazonaws.com/duolingo-images/avatars/22524/PALdVtqnHa',
    'bio': '',
    'cohort': 17,
    'contribution_points': 0,
    'created': '1 year ago',
    'fullname': 'Kartik',
    'gplus_id': None,
    'id': 22524,
    'invites_left': 3,
    'learning_language_string': 'French',
    'location': 'Toronto',
    'num_followers': 3,
    'num_following': 4,
    'twitter_id': None,
    'username': 'kartik',
    'ui_language': 'en'
}

Get Settings

lingo.get_settings()

Returns the user's settings.

# Sample Request
lingo  = duolingo.Duolingo('kartik', '...')
print(lingo.get_user_settings())

# Sample Response
{
    'deactivated': False,
    'is_follower_by': False,
    'is_following': False,
    'notify_comment': True
}

Get Languages

lingo.get_languages(abbreviations)

Returns a list of languages the user is learning.

# Sample Request
lingo  = duolingo.Duolingo('kartik', '...')
print(lingo.get_languages(abbreviations=True))
Parameters

abbreviations (boolean) optional
--Returns the list of languages as abbreviations. Default=False.

# Sample Response
['fr', 'de', 'es']

Get Friends

lingo.get_friends()

Returns a list of user's friends, their total points earned, and the languages they are learning. The current user is included in this list.

# Sample Request
lingo  = duolingo.Duolingo('kartik', '...')
print(lingo.get_friends())

# Sample Response
[{'languages': ['French', 'Spanish', 'German', 'Italian'],
  'points': 4791,
  'username': 'apmechev'},
 {'languages': ['French', 'Spanish'],
  'points': 1810,
  'username': 'jlfwong'},
 {'languages': ['French', 'German', 'Spanish'],
  'points': 754,
  'username': 'kartik'},
 {'languages': ['Spanish', 'French'], 'points': 718, 'username': 'vhisko'},
 {'languages': ['French', 'German'],
  'points': 579,
  'username': 'warrench04'}]

Get Calendar

lingo.get_calendar(language_abbr)

Returns the user's last action.

Parameters

language_abbr (string) optional
--Abbreviation of a given language. Default=None.

Get Streak Information

lingo.get_streak_info()

Returns the current site-wide streak, including daily goal information, and whether the streak has been extended today.

# Sample Request
lingo  = duolingo.Duolingo('kartik', '...')
print(lingo.get_streak_info())

# Sample Response
{
    'site_streak': 141,
    'daily_goal': 30,
    'streak_extended_today': True
}

Get Leaderboard

lingo.get_leaderboard(unit, before)

Returns an ordered list containing the logged user leaderboard. You need to indicate unit as week or month to get the desired result. The before argument comes with the time.time() function, but if you need to know your leaderboard for a different date, you can pass the date in a epoch format.

# Sample Request
lingo = duolingo.Duolingo('yurireis5', '...')
print(lingo.get_leaderboard('week'))
Parameters

unit (string) optional
--Receive leaderboard data in specified units. The units week and month are recommended to receive desired results. Default=None.
before (string) optional
--Receive leaderboard data up to a specified date. Default=time.time().

# Sample Response
[
    {
        'unit': 'week',
        'id': 945238,
        'points': 280,
        'username': 'leticiabohrer'
    },
    {
        'unit': 'week',
        'id': 125621306,
        'points': 63,
        'username': 'Candice460698'
    },
    ...
]

Get Daily XP progress

lingo.get_daily_xp_progress()

Returns an ordered list containing the logged user leaderboard. You need to indicate unit as week or month to get the desired result. The before argument comes with the time.time() function, but if you need to know your leaderboard for a different date, you can pass the date in a epoch format. Returns a dict with 3 keys: 'xp_goal', 'lessons_today', and 'xp_today'.

  • xp_goal: Is your daily XP goal (int)
  • lessons_today: A list of the lesson names which have been completed today
  • xp_today: How much XP you have got today (int)

This method does not work if the username has been set to something else after login.

# Sample Request
lingo = duolingo.Duolingo('yurireis5', '...')
print(lingo.get_daily_xp_progress())

# Sample Response
{
    'xp_goal': 10, 
    'lessons_today': [], 
    'xp_today': 0
}

Buy Item

lingo.buy_item(item_name, language_abbr)

Buy a specific item in the shop. Returns the name of the item and the date and time of purchase.

# Sample Request
lingo  = duolingo.Duolingo('kartik', '...')
print(lingo.buy_item('streak_freeze', 'en'))
Parameters

item_name (string) required
--The name of the item to buy.
language_abbr (string) required
--Abbreviation of a given language.

# Sample Response
{
    'streak_freeze': '2017-01-10 02:39:59.594327'
}

Note: This will return HTTP Status Code 400 if the item can't be bought.

Buy Streak Freeze

lingo.buy_streak_freeze()

Buy a Streak on Ice extension, if the account has enough Lingots and is not yet equipped with the extension. Returns True if the extension was bought, False otherwise.

# Sample Request
lingo  = duolingo.Duolingo('kartik', '...')
print(lingo.buy_streak_freeze())

# Sample Response
True

Set username

lingo.set_username(username)

Sets the username, and reloads user data. This then allows you to read another user's information via the same API. This will not work with the get_daily_xp_progress() method, and obviously will not allow you to buy items for other users.

# Sample Request
lingo = Duolingo("kartik","...")
print(lingo.get_languages())
lingo.set_username("kartik2")
print(lingo.get_languages())

# Sample response
['French', 'German', 'Russian', 'Chinese', 'Portuguese', 'Spanish']
['French']

Get Language Details

lingo.get_language_details(language_name)

Returns the language details for a given language, including the current streak, the level, and total number of points.

# Sample Request
lingo  = duolingo.Duolingo('kartik', '...')
print(lingo.get_language_details('French'))
Parameters

language_name (string) required
--The name of a given language.

# Sample Response
{
    'current_learning': True,
    'language': 'fr',
    'language_string': 'French',
    'learning': True,
    'level': 6,
    'points': 604,
    'streak': 0
}

Get Language Progress

lingo.get_language_progress(language_abbr)

Returns the language progress for a given language.

# Sample Request
lingo  = duolingo.Duolingo('kartik', '...')
print(lingo.get_language_progress('fr'))
Parameters

language_abbr (string) required
--Abbrieviation of a given language.

# Sample Response
{
    'language': 'fr',
    'language_string': 'French',
    'level_left': 146,
    'level_percent': 51,
    'level_points': 300,
    'level_progress': 154,
    'next_level': 7,
    'num_skills_learned': 15,
    'points': 604,
    'points_rank': 3,
    'streak': 0
}

Get Known Topics

lingo.get_known_topics(language_abbr)

Returns a list containing the names of the known topics. See get_learned_skills to return entire skill data.

Note: Order is not guaranteed.

# Sample Request
lingo  = duolingo.Duolingo('kartik', '...')
print(lingo.get_known_topics('fr'))
Parameters

language_abbr (string) required
--Abbrieviation of a given language.

# Sample Response
[
    'Colors',
    'Basics 2',
    'Animals',
    'Possessives',
    'Verbs: \xcatre / Avoir',
    'Clothing',
    'Food',
    'Questions',
    'Basics',
    'Verbs: Present 1',
    'Plurals',
    'Common Phrases',
    'Adjectives 1'
]

Get Unknown Topics

lingo.get_unknown_topics(language_abbr)

Returns a list containing the names of the unlearned topics.

Note: Order is not guaranteed.

# Sample Request
lingo  = duolingo.Duolingo('kartik', '...')
print(lingo.get_unknown_topics())
Parameters

language_abbr (string) required
--Abbrieviation of a given language.

# Sample Response
[
    'The',
    'Accusative Case',
    'Nature 1'
]

Get Golden Topics

lingo.get_golden_topics(language_abbr)

Returns a list containing the names of fully reviewed, or "golden", topics.

Note: Order is not guaranteed.

# Sample Request
lingo  = duolingo.Duolingo('kartik', '...')
print(lingo.get_golden_topics('fr'))
Parameters

language_abbr (string) required
--Abbrieviation of a given language.

# Sample Response
[
    'Colors',
    'Basics 2',
    'Animals',
    'Possessives',
    'Verbs: \xcatre / Avoir',
    'Clothing',
    'Verbs: Present 1',
    'Plurals',
    'Common Phrases',
    'Adjectives 1'
]

Get Reviewable Topics

lingo.get_reviewable_topics(language_abbr)

Returns a list containing the names of learned, but not fully "golden", topics.

Note: Order is not guaranteed.

# Sample Request
lingo  = duolingo.Duolingo('kartik', '...')
print(lingo.get_reviewable_topics('fr'))
Parameters

language_abbr (string) required
--Abbrieviation of a given language.

# Sample Response
[
    'Food',
    'Questions',
    'Basics'
]

Get Known Words

lingo.get_known_words(language_abbr)

Returns a set containing known words of a given language.

# Sample Request
lingo  = duolingo.Duolingo('kartik', '...')
print(lingo.get_known_words('fr'))
Parameters

language_abbr (string) required
--Abbrieviation of a given language.

# Sample Response
[
    'absolument',
    'accept\xe9',
    'acier',
    'actuellement',
    'adopt\xe9',
    'affirme',
    'agissant',
    'agit',
    'agr\xe9able',
    'ai',
    'aient',
    'ailes',
    'aime',
    'aimerais'
]

Get Related Words

lingo.get_related_words(word, language_abbr)

Returns a list of "related words" from the user's vocabulary list. For example, for the German verb "gehen", get_related_words will return a list of miscellaneous conjugations like "gehe" and "gingen".

Note: The dictionaries it returns are identical in format to those returned by get_vocabulary.

# Sample Request
lingo  = duolingo.Duolingo('kartik', '...')
 print(lingo.get_related_words('aller'))
Parameters

word (string) required
--The word you want to retrieve related words for.
language_abbr (string) optional
--Abbreviation of a given language. Default=None.

# Sample Response
[
   {
       'last_practiced': '2015-05-27T06:01:18Z',
       'strength': 0.991741,
       'strength_bars': 4,
       'infinitive': 'aller',
       'lexeme_id': '51a2297870df84c13c7ce0b5f987ae70',
       'normalized_string': 'allait',
       'pos': 'Verb',
       'id': '51a2297870df84c13c7ce0b5f987ae70',
       'last_practiced_ms': 1432706478000.0,
       'gender': None,
       'skill': 'Verbs: Past Imperfect',
       'word_string': 'allait',
       'related_lexemes': [...],
       'skill_url_title': 'Verbs:-Past-Imperfect'
   },
   ...
]

Get Learned Skills

lingo.get_learned_skills(language_abbr)

Returns an ordered list containing the names of the known topics by date learned. Differs from get_known_topics in that it returns the entire skill data of each skill learned, rather than only the name.

# Sample Request
lingo  = duolingo.Duolingo('kartik', '...')
print(lingo.get_learned_skills('fr'))
Parameters

language_abbr (string) required
--Abbrieviation of a given language.

# Sample Response
[
    {
        'language_string': 'French',
        'dependency_order': 0,
        'dependencies_name': [],
        'practice_recommended': False,
        'learning_threshold': 0,
        'disabled': False,
        'more_lessons': 0,
        'test_count': 3,
        'missing_lessons': 0,
        'lesson': False,
        'progress_percent': 100.0,
        'id': 'aad5e3a9fc5bb6a9b55a4d20d40c3f27',
        'description': '',
        'category': '',
        'num_lessons': 4,
        'language': 'fr',
        'strength': 0.25,
        'beginner': True,
        'title': 'Basics 1',
        'coords_y': 1,
        'coords_x': 2,
        'url_title': 'Basics-1',
        'test': True,
        'lesson_number': 1,
        'learned': True,
        'num_translation_nodes': 0,
        'learning_threshold_percentage': 0,
        'icon_color': 'blue',
        'index': '0',
        'bonus': False,
        'explanation': (string containing HTML of explanation),
        'num_lexemes': 30,
        'num_missing': 0,
        'left_lessons': 0,
        'dependencies': [],
        'known_lexemes': [...],
        'words': [list of words contained in the lesson],
        'path': [],
        'achievements': [],
        'short': 'Basics 1',
        'locked': False,
        'name': 'BASICS',
        'comment_data': {},
        'new_index': 1,
        'changed': False,
        'has_explanation': True,
        'mastered': True
    },
    ...
]

Get Language from Abbreviation

lingo.get_language_from_abbr(language_abbr)

When the language_abbr of a language is known, but the full language name is not, you can use this method to return the language name. This only works for languages that the user is learning.

# Sample Request
lingo  = duolingo.Duolingo('kartik', '...')
print(lingo.get_language_from_abbr('fr'))
Parameters

language_abbr (string) required
--Abbrieviation of a given language.

# Sample Response
'French'

Get Abbreviation Of

lingo.get_abbreviation_of(language_name)

When the language_name of a language is known, but the language abbreviation is not, you can use this method to get the abbreviation.

Note: This only works for languages that the user is learning.

# Sample Request
lingo  = duolingo.Duolingo('kartik', '...')
print(lingo.get_abbreviation_of('French'))
Parameters

language_name (string) required
--The name of a given language.

# Sample Response
'fr'

Get Translations

lingo.get_translations(words)

Returns the translations of a list of words passed to it. By default, the source is assumed to be the language of the user's Duolingo UI, and the target is assumed to be the user's current language, as of login time. The returned object is a dictionary containing a key for each item in the words list, with a list of translations as its value.

# Sample Request
lingo  = duolingo.Duolingo('kartik', '...')
lingo.get_translations(['de', 'du'], source='de', target='fr')
Parameters

words (list) required
--The list of words you want to translate.
source (string) optional
--Specifies a source language to translate the words from. Default=None.
target (string) optional
--Specifies a target language to translate the words into. Default=None.

# Sample Response
{
    'de': ['zu', 'von', 'des', 'an', 'auf', 'aus', 'mit', 'um',
            'vor', '\xfcber'],
    'du': ['der', 'nach', 'zur', '\u2205']
}

Get Vocabulary

lingo.get_vocabulary()

Gets the user's vocabulary for a given language. If language_abbr is none, the user's current language is used.

#Sample Request
lingo  = duolingo.Duolingo('kartik', '...')
print(lingo.get_vocabulary(language_abbr='de'))
Parameters

language_abbr (string) optional
--Abbrieviation of a given language.

# Sample Response
{
    language_string: "German",
    learning_language: "de",
    from_language: "en",
    language_information: {...},
    vocab_overview: [
    {
        strength_bars: 4,
        infinitive: null,
        normalized_string: "am",
        pos: "Preposition",
        last_practiced_ms: 1436422057000,
        skill: "Dative Case",
        related_lexemes: [
        "bb7397cbcb9f6665fcba49eced7b8619"
        ],
        last_practiced: "2015-07-09T06:07:37Z",
        strength: 0.999987,
        skill_url_title: "Dative-Case",
        gender: "Masculine",
        id: "2ffcc3aea9f3005d69b38083a6cac19d",
        lexeme_id: "2ffcc3aea9f3005d69b38083a6cac19d",
        word_string: "am"
        },
        ...
    ]
}

Get Language Voices

lingo.get_language_voices(language_abbr)

Returns a list of voices available in a given language. The list will always contain at least one voice, but that voice might not always be named 'default'. For instance, the only voice available for Turkish is named 'filiz'.

# Sample Request
lingo  = duolingo.Duolingo('kartik', '...')
print(lingo.get_language_voices('fr'))
Parameters

language_abbr (string) required
--Abbrieviation of a given language.

['default', 'mathieu']

Get Audio URL

lingo.get_audio_url(word)

Returns the path to an audio file containing the pronunciation of the word given. The language defaults to the user's current learning language. The voice used by default is randomly selected from Duolingo's available voices. To get a specific voice, pass the voice parameter with the name of the voice. To get the default voice (which is mostly an implementation detail), set random to False without passing a voice.

# Sample Request
lingo  = duolingo.Duolingo('kartik', '...')
print(lingo.get_audio_url('bonjour'))
Parameters

word (string) required
--The word you want an audio file for.
language_abbr (string) optional
--Abbrieviation of a given language. Default=None.
rand (boolean) optional
--Whether to return a randomly selected language voice. Default=True.
voice (string) optional
--The name of a specific language voice. Default=None.

# Sample Response
'https://d7mj4aqfscim2.cloudfront.net/tts/fr/token/bonjour'

duolingo's People

Contributors

aisha-w avatar andreasscherbaum avatar aviadsteps avatar cybershadow avatar danielnoord avatar diogo-aos avatar dmadisetti avatar duststorm avatar goatgoose avatar icole avatar idefux avatar igorskh avatar irio avatar jaschilz avatar jay754 avatar joshcoales avatar kartiktalwar avatar klaw23 avatar marciomazza avatar mustafaulker avatar orbin avatar piggydoughnut avatar sckelemen avatar spanglelabs avatar tschuy avatar yurireeis avatar zulupro avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

duolingo's Issues

buy_streak_freeze() is broken again

Updated my script to the latest version of this repository, and the "buy_streak_freeze()" function stopped working, with an exception. Previous versions returned something similar to the following:

{'Date': 'Thu, 20 Feb 2020 13:23:12 GMT', 'Content-Type': 'application/json; charset=utf-8', 'Content-Length': '36', 'Connection': 'keep-alive', 'pragma': 'no-cache', 'x-tid': '***', 'cache-control': 'no-store, no-cache, must-revalidate, proxy-revalidate', 'surrogate-control': 'no-store', 'x-runtime': '0.04843', 'x-ws': 'UK', 'server': 'duo-api', 'expires': '0', 'x-uid': '***', 'x-frame-options': 'SAMEORIGIN', 'x-content-type-options': 'nosniff', 'x-xss-protection': '1 ; mode=block', 'referrer-policy': 'no-referrer', 'x-envoy-upstream-service-time': '94', 'x-duo-request-host': 'www.duolingo.com'}

{"error": "ALREADY_HAVE_STORE_ITEM"}

The current version returns:

{'Date': 'Thu, 20 Feb 2020 13:20:37 GMT', 'Content-Type': 'application/json', 'Content-Length': '396', 'Connection': 'keep-alive', 'server': 'duo-api', 'set-cookie': '_pxhd=***; path=/; expires=Fri, 19 Feb 2021 13:20:37 GMT;', 'cache-control': 'no-cache, no-store, max-age=0, must-revalidate', 'pragma': 'no-cache', 'expires': '0', 'x-content-type-options': 'nosniff', 'x-frame-options': 'DENY', 'x-xss-protection': '1 ; mode=block', 'referrer-policy': 'no-referrer', 'x-envoy-upstream-service-time': '78', 'x-duo-request-host': 'www.duolingo.com'}

{"blockScript": "/***/captcha/captcha.js?a=c&u=***&v=&m=0", "vid": "", "jsRef": "", "hostUrl": "/***/xhr", "customLogo": null, "appId": "***", "uuid": "***", "logoVisibility": "hidden", "jsClientSrc": "/***/init.js", "firstPartyEnabled": "true", "refId": "***", "cssRef": ""}

Why is the host returning a different response based on what version (and which features) this client library is using? And apparently starts asking for a captcha to solve?

Can't create Duolingo object, again

======================================================================
ERROR: tests (unittest.loader._FailedTest)
----------------------------------------------------------------------
ImportError: Failed to import test module: tests
Traceback (most recent call last):
  File "/lib/python3.5/unittest/loader.py", line 428, in _find_test_path
    module = self._get_module_from_name(name)
  File "/lib/python3.5/unittest/loader.py", line 369, in _get_module_from_name
    __import__(name)
  File "tests.py", line 9, in <module>
    class DuolingoTest(unittest.TestCase):
  File "tests.py", line 10, in DuolingoTest
    lingo = duolingo.Duolingo(USERNAME, password=PASSWORD)
  File "duolingo.py", line 36, in __init__
    self.user_data = Struct(**self._get_data())
  File "duolingo.py", line 177, in _get_data
    get = self._make_req(self.user_url).json()
  File "/lib/python3.5/site-packages/requests/models.py", line 897, in json
    return complexjson.loads(self.text, **kwargs)
  File "/lib/python3.5/json/__init__.py", line 319, in loads
    return _default_decoder.decode(s)
  File "/lib/python3.5/json/decoder.py", line 342, in decode
    raise JSONDecodeError("Extra data", s, end)
json.decoder.JSONDecodeError: Extra data: line 1 column 5 (char 4)

Using 0848a07, the latest commit on master as of writing this.

Reduce duplicate API calls

Right now we're calling the same URL a lot when it's not necessary. We should cache results whenever possible.

Mobile vs web rupee_wager

Hi,

My account got switched from lingots to gems (on mobile app)
Open on browser I still see only lingots and my rupee_wager is equipt but on mobile I can buy one for 50 gems.

Anything in the code to buy the 50 gems version?

Or I'm just part of A/B testing beta group?

here JSON from browser (no idea how to get this from mobile app):

		{
			"name": "Double or Nothing",
			"localizedDescription": "Attempt to double your five lingot wager by maintaining a seven day streak.",
			"price": 5,
			"type": "misc",
			"id": "rupee_wager",
			"currencyType": "XLG"
		},
		{
			"name": "Double or Nothing",
			"localizedDescription": "Attempt to double your two lingot wager by maintaining a three day streak.",
			"price": 2,
			"type": "misc",
			"id": "rupee_wager_3",
			"currencyType": "XLG"
		},

Can't create Duolingo object

I installed and imported duolingo. I tried with and without adding my password, and both times I got this error message:

Traceback (most recent call last): File "/Users/cat/PycharmProjects/duolingo/duolingo_proj.py", line 2, in <module> lingo = duolingo.Duolingo('CatherineD576833') File "/anaconda/lib/python3.5/site-packages/duolingo.py", line 25, in __init__ self.user_data = Struct(**self._get_data()) File "/anaconda/lib/python3.5/site-packages/duolingo.py", line 61, in _get_data get = self.session.get(self.user_url).json() File "/anaconda/lib/python3.5/site-packages/requests/models.py", line 812, in json return complexjson.loads(self.text, **kwargs) File "/anaconda/lib/python3.5/json/__init__.py", line 319, in loads return _default_decoder.decode(s) File "/anaconda/lib/python3.5/json/decoder.py", line 339, in decode obj, end = self.raw_decode(s, idx=_w(s, 0).end()) File "/anaconda/lib/python3.5/json/decoder.py", line 357, in raw_decode raise JSONDecodeError("Expecting value", s, err.value) from None json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

If I typed something wrong, the documentation should be edited to provide a clearer example. If I typed this right, then there is an issue that needs to be fixed. Seems like a great API but I can't access any of it right now if I can't even make an object.

Mobile app API inventory

General

https://www.duolingo.com/api/1/version_info
https://www.duolingo.com/api/1/users/show?id=?????
https://www.duolingo.com/api/1/store/get_items

Skills:

  1. main skill page
    https://api.duolingo.com/api/1/skills/show?id=?????
  2. lessons
    https://api.duolingo.com/api/1/sessions?device=mobile&select_capable=true&offline=true&lesson_number=???&type=lesson&skill_id=????&speak_capable=true

Sentence:
http://d2.duolingo.com/words/hints/de/en?sentence=plus+separated+words.&format=new
It appears this API can take arbitrary sentences.

Other
The cloudflare URL should be replaced with static.duolingo.com, which is the value returned by https://www.duolingo.com/api/1/version_info for the TTS CDN url.

newbie / noob info (install /run)

Hi, had trouble to install via pip here some dependencies I needed.
This info might be useful for other trying to use the API.

The pip repository version is from 2015 (only has subset of functions)
https://pypi.python.org/pypi/duolingo-api

to install from git use:
pip install --upgrade https://github.com/KartikTalwar/Duolingo/tarball/master

btw installed it on my Pi running OSMC

pip install --upgrade setuptools
pip install wheel

Here my full sequence for other ppl reference.

osmc@myraspi:~$ python -V
Python 2.7.13

osmc@myraspi:~$ sudo apt-get install python-pip
Reading package lists... Done
Building dependency tree        
Reading state information... Done
The following packages were automatically installed and are no longer required:
...
Use 'sudo apt autoremove' to remove them.
The following additional packages will be installed:
  python-pip-whl
Recommended packages:
  python-all-dev python-setuptools python-wheel
The following NEW packages will be installed:
  python-pip python-pip-whl
0 upgraded, 2 newly installed, 0 to remove and 7 not upgraded.
Need to get 1,585 kB of archives.
After this operation, 2,302 kB of additional disk space will be used.
Do you want to continue? [Y/n] y
Get:1 http://ftp.debian.org/debian stretch/main armhf python-pip-whl all 9.0.1-2 [1,406 kB]
Get:2 http://ftp.debian.org/debian stretch/main armhf python-pip all 9.0.1-2 [179 kB]
Fetched 1,585 kB in 1s (845 kB/s)   
Selecting previously unselected package python-pip-whl.
(Reading database ... 67869 files and directories currently installed.)
Preparing to unpack .../python-pip-whl_9.0.1-2_all.deb ...
Unpacking python-pip-whl (9.0.1-2) ...
Selecting previously unselected package python-pip.
Preparing to unpack .../python-pip_9.0.1-2_all.deb ...
Unpacking python-pip (9.0.1-2) ...
Setting up python-pip-whl (9.0.1-2) ...
Setting up python-pip (9.0.1-2) ...

osmc@myraspi:~$ pip install --upgrade setuptools
Collecting setuptools
  Downloading setuptools-38.5.1-py2.py3-none-any.whl (489kB)
    100% |████████████████████████████████| 491kB 399kB/s 
Installing collected packages: setuptools
Successfully installed setuptools-38.5.1

osmc@myraspi:~$ pip install wheel
Collecting wheel
  Downloading wheel-0.30.0-py2.py3-none-any.whl (49kB)
    100% |████████████████████████████████| 51kB 656kB/s 
Installing collected packages: wheel
Successfully installed wheel-0.30.0

osmc@myraspi:~$ #######
osmc@myraspi:~$ # osmc@myraspi:~$ pip install duolingo-api #2015 version, 
osmc@myraspi:~$ pip install --upgrade https://github.com/KartikTalwar/Duolingo/tarball/master #latest
osmc@myraspi:~$ #######output below is from 2015 pip repository version
Collecting duolingo-api
  Using cached duolingo-api-0.3.tar.gz
Collecting Werkzeug (from duolingo-api)
  Using cached Werkzeug-0.14.1-py2.py3-none-any.whl
Collecting requests (from duolingo-api)
  Using cached requests-2.18.4-py2.py3-none-any.whl
Collecting idna<2.7,>=2.5 (from requests->duolingo-api)
  Using cached idna-2.6-py2.py3-none-any.whl
Collecting urllib3<1.23,>=1.21.1 (from requests->duolingo-api)
  Using cached urllib3-1.22-py2.py3-none-any.whl
Collecting certifi>=2017.4.17 (from requests->duolingo-api)
  Using cached certifi-2018.1.18-py2.py3-none-any.whl
Collecting chardet<3.1.0,>=3.0.2 (from requests->duolingo-api)
  Using cached chardet-3.0.4-py2.py3-none-any.whl
Building wheels for collected packages: duolingo-api
  Running setup.py bdist_wheel for duolingo-api ... done
  Stored in directory: /home/osmc/.cache/pip/wheels/1d/c4/ca/764b01b2c268cf7d371416d70298273b599d7364edc5ac741a
Successfully built duolingo-api
Installing collected packages: Werkzeug, idna, urllib3, certifi, chardet, requests, duolingo-api
Successfully installed Werkzeug-0.14.1 certifi-2018.1.18 chardet-3.0.4 duolingo-api-0.3 idna-2.6 requests-2.18.4 urllib3-1.22


osmc@myraspi:~$ #######How to remove pip repository version
osmc@myraspi:~$ pip uninstall duolingo-api

osmc@myraspi:~$ #######Issues during install:

osmc@myraspi:~$ sudo pip install duolingo-api
Collecting duolingo-api
  Downloading duolingo-api-0.3.tar.gz
    Complete output from command python setup.py egg_info:
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
    ImportError: No module named setuptools
    
    ----------------------------------------
Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-build-y1lQZS/duolingo-api/


osmc@myraspi:~$ pip install duolingo-api
Collecting duolingo-api
  Using cached duolingo-api-0.3.tar.gz
Collecting Werkzeug (from duolingo-api)
  Downloading Werkzeug-0.14.1-py2.py3-none-any.whl (322kB)
    100% |████████████████████████████████| 327kB 669kB/s 
Collecting requests (from duolingo-api)
  Downloading requests-2.18.4-py2.py3-none-any.whl (88kB)
    100% |████████████████████████████████| 92kB 1.8MB/s 
Collecting idna<2.7,>=2.5 (from requests->duolingo-api)
  Downloading idna-2.6-py2.py3-none-any.whl (56kB)
    100% |████████████████████████████████| 61kB 2.0MB/s 
Collecting urllib3<1.23,>=1.21.1 (from requests->duolingo-api)
  Downloading urllib3-1.22-py2.py3-none-any.whl (132kB)
    100% |████████████████████████████████| 133kB 1.5MB/s 
Collecting certifi>=2017.4.17 (from requests->duolingo-api)
  Downloading certifi-2018.1.18-py2.py3-none-any.whl (151kB)
    100% |████████████████████████████████| 153kB 1.3MB/s 
Collecting chardet<3.1.0,>=3.0.2 (from requests->duolingo-api)
  Downloading chardet-3.0.4-py2.py3-none-any.whl (133kB)
    100% |████████████████████████████████| 143kB 1.5MB/s 
Building wheels for collected packages: duolingo-api
  Running setup.py bdist_wheel for duolingo-api ... error
  Complete output from command /usr/bin/python -u -c "import setuptools, tokenize;__file__='/tmp/pip-build-I5_owF/duolingo-api/setup.py';f=getattr(tokenize, 'open', open)(__file__);code=f.read().replace('\r\n', '\n');f.close();exec(compile(code, __file__, 'exec'))" bdist_wheel -d /tmp/tmphGmGNwpip-wheel- --python-tag cp27:
  usage: -c [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...]
     or: -c --help [cmd1 cmd2 ...]
     or: -c --help-commands
     or: -c cmd --help
  
  error: invalid command 'bdist_wheel'
  
  ----------------------------------------
  Failed building wheel for duolingo-api
  Running setup.py clean for duolingo-api
Failed to build duolingo-api
Installing collected packages: Werkzeug, idna, urllib3, certifi, chardet, requests, duolingo-api
  Running setup.py install for duolingo-api ... done
Successfully installed Werkzeug-0.14.1 certifi-2018.1.18 chardet-3.0.4 duolingo-api-0.3 idna-2.6 requests-2.18.4 urllib3-1.22

Get all courses of an user

The idea is get the all courses (and levels, crowns, etc...) that a user is involved into.
If you study several languages from your mother language, for example, English, you can get that info through function "lingo.get_languages". But in case you are studying several languages through some "mother" languages, the information that this function gets is partial. For example: an user studies Chinese through English (DUOLINGO_ZH-CN_EN), and English through Spanish (DUOLINGO_EN_ES). If someone call that function only it will return one of the two pieces of info, depending on what is the mother language that actually is set on the session.
The solution is getting the info through the URL:
https://www.duolingo.com/2017-06-30/users/{ID_OF_USER_INTEGER}?fields=courses

Is it possible to implement this function? If the response is positive: could you please tell me how and I will try to make it? Thanks in advance for your help!

Tests are failing

The last commit to master has failing tests. The link is:

https://travis-ci.org/github/KartikTalwar/Duolingo/jobs/678833117

The last one with green status is this one, from four months ago:

https://travis-ci.org/github/KartikTalwar/Duolingo/jobs/647856619

Locally, two tests fail for me:

(duolingo-3.7.7) irio@DESKTOP-CD59K3G:~/workspace/Duolingo$ pytest tests.py
================================================= test session starts ==================================================platform linux -- Python 3.7.7, pytest-5.4.2, py-1.8.1, pluggy-0.13.1
rootdir: /home/irio/workspace/Duolingo, inifile: setup.cfg
plugins: cov-2.9.0
collected 54 items

tests.py ..F..........................F........................                                                  [100%]

======================================================= FAILURES =======================================================___________________________________________ DuolingoTest.test_get_audio_url ____________________________________________
self = <tests.DuolingoTest testMethod=test_get_audio_url>

    def test_get_audio_url(self):
        # Setup
        vocab = self.lingo.get_vocabulary()
        word = vocab['vocab_overview'][0]['normalized_string']
        # Test
        response = self.lingo.get_audio_url(word)
>       assert isinstance(response, str)
E       AssertionError: assert False
E        +  where False = isinstance(None, str)

tests.py:232: AssertionError
_____________________________________ DuolingoOtherUsernameTest.test_get_audio_url _____________________________________
self = <tests.DuolingoOtherUsernameTest testMethod=test_get_audio_url>

    def test_get_audio_url(self):
        # Setup
        vocab = self.lingo.get_vocabulary()
        word = vocab['vocab_overview'][0]['normalized_string']
        # Test
        response = self.lingo.get_audio_url(word)
>       assert isinstance(response, str)
E       AssertionError: assert False
E        +  where False = isinstance(None, str)

tests.py:232: AssertionError

----------- coverage: platform linux, python 3.7.7-final-0 -----------
Name          Stmts   Miss  Cover   Missing
-------------------------------------------
duolingo.py     355    102    71%   75, 88, 101, 106-110, 114-115, 140, 143, 165-189, 196-203, 213-222, 230, 260, 314, 321, 329, 354, 362, 457-458, 463, 476-481, 485-491, 496-500, 503-513, 518, 524, 532-542, 566-574, 577-580, 607-608
tests.py        222      7    97%   80, 233-236, 262, 269
-------------------------------------------
TOTAL           577    109    81%

=============================================== short test summary info ================================================FAILED tests.py::DuolingoTest::test_get_audio_url - AssertionError: assert False
FAILED tests.py::DuolingoOtherUsernameTest::test_get_audio_url - AssertionError: assert False

How to find XP Progress

The Duolingo website shows the daily XP progress (like: 79 out of goal 20), however it seems this value is not available through the API.

Anyone knows how to fetch the daily progress using Python?

Bad URL on get_audio_url(url)

I'm using this project to download word lists for users; some of this data involves downloading the audio using get_audio_url(url). For most words, the URL given works; for others, I get an error'd XML page. Some of these errors are simply due to capitalization -- for example, 'Junge' doesn't work, but 'junge' does. 'Mann' doesn't work, but 'mann' does. Etc. Most of these, I can account for by using a simple try-catch which tests the lowercase version if the regular url returned by get_audio_url() fails. However, there are other URLs that don't work at all.

https://d7mj4aqfscim2.cloudfront.net/tts/de/token/kleide
https://d7mj4aqfscim2.cloudfront.net/tts/de/token/kleidungen
https://d7mj4aqfscim2.cloudfront.net/tts/de/token/guten%20morgen
https://d7mj4aqfscim2.cloudfront.net/tts/de/token/fresse

Neither by making them upper, lower, a mix, it doesn't work. I honestly don't know how these URLs are arrived at in the first place, so there wasn't much troubleshooting I could think of. I know Duolingo has audio of these because I have heard them, so it can't be that they don't exist.

Any ideas?

buy_streak_freeze() is no longer working: error 403 is returned

I'm using the buy_streak_freeze() function in a script, and recently it started hitting an error. Enabling debugging brings up the following:

DEBUG: https://www.duolingo.com:443 "POST /2017-06-30/users/<id>/purchase-store-item HTTP/1.1" 403 396

Was the API changed again?

get_learned_skills does not work for Romanian

Trying to execute lingo.get_learned_skills('ro') raises a Loop exception for me.

The problem seems to be, that it has two skills with the name "Properties" and one of them depends (?) on the other. So essentially it is executing this code:

    skills = [{"name":"Properties","dependencies_name":[]},{"name":"Properties","dependencies_name":["Properties"]}]
    duolingo.Duolingo._compute_dependency_order_func(skills)

Here are the relevant attributes from the server response:

[
    {
        "id": "8773494720f14d7aa644c87598cd57ce",
        "name": "Properties",
        "dependencies": [
            "Prepositions 1 (for Acc.Case)",
            "Demonstrative Pronouns and Pronominal Adjectives"
        ],
        "dependencies_name": [
            "Prepositions",
            "Demonstrative Pronouns and Pronominal Adjectives"
        ]
    },
    {
        "id": "1bca2b5b9e06e1df387f97966764e1d2",
        "name": "Properties",
        "dependencies": [
            "Units of Measurement",
            "Dative Pronouns",
            "Objects"
        ],
        "dependencies_name": [
            "Properties",
            "Genitive-Dative Pronouns",
            "Objects"
        ]
    }
]

It looks like the error is that dependencies uses "Units of Measurement" while dependencies_name uses "Properties". So I'm not sure if it is a bug of this library or by Duolingo.

get_audio_url, language_voices, etc.

It seems that you "scrape" the address of the audio URL from the html gotten from the user data home page html. Currently, there are no audio links on the home page. Are there other plans to get this working or will it just stay broken? I haven't been able to find a way to get it without referring to the network access data on the inspection of a dictionary page. Theres no "voice" to select in the user data.

e.g.
https://www.duolingo.com/dictionary/-/-/2706a7183676568fc4467961d06490b7
Inspect -> Network:
https://d1vq87e9lcf771.cloudfront.net/zhiyu/968adc4657e435d56c33bb8f955f8591
or
https://d7mj4aqfscim2.cloudfront.net/tts/zs/jiaoling/token/她

Edit: reguarding a voice name, I did find Zhiyu's name deep in the dicts. There is hope. THe formatting of the doct keys are mostly consistent too (ex: tts_polly_de_marlene, tts_polly_ja_takumi, tts_polly_fr_lea, tts_polly_zh-CN_zhiyu). I guess the best way to do this will be to extract the text after the last underscore of the key that contains the language abbreviation. self.user_data['informant_reference']

Edit2: Just realized that the Chinese abbreviation is 'zs' and that is not contained within the string that references Zhiyu, so that method goes out the window for Chinese.

get_known_words()

a = duolingo.Duolingo('xyz')
a.get_settings()
{'deactivated': False, 'notify_comment': True, 'is_following': False, 'is_follower_by': False}
a.get_known_words('fr')
Traceback (most recent call last):
File "", line 1, in 
File "build/bdist.macosx-10.9-x86_64/egg/duolingo/init.py", line 127, in get_known_words
for word in self.user_data.language_data[lang]['skills']:
KeyError: 'fr'

I tried it just like in the documentation.
Of course, no arguments leads to this :

a.get_known_words()
Traceback (most recent call last):
File "", line 1, in 
TypeError: get_known_words() takes exactly 2 arguments (1 given)

Could you help me out ?

a.

buy_streak_freeze() always buys a new streak

Started a couple days ago: every time I run buy_streak_freeze(), I don't get a AlreadyHaveStoreItemException exception anymore, but it started "charging" me Lingots every time - even though I already have a streak equipped.

Anyone knows what's wrong there, or how to fix/prevent that?

pypi.python.org old version

Hello,

I check the version of the "duolingo-api" package on pypi and it seems that version 0.3 is really old (uploaded 2015-11-22) is it possible that you can upload a new version?

Thank you in advance.

get_translations is not working

When I am passing a list with source and target it shows:

Traceback (most recent call last):
  File "/home/shafquat/DuolingoWordList/venv/lib/python3.7/site-packages/duolingo.py", line 230, in get_translations
    return request.json()
  File "/home/shafquat/DuolingoWordList/venv/lib/python3.7/site-packages/requests/models.py", line 897, in json
    return complexjson.loads(self.text, **kwargs)
  File "/usr/lib/python3.7/json/__init__.py", line 348, in loads
    return _default_decoder.decode(s)
  File "/usr/lib/python3.7/json/decoder.py", line 337, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "/usr/lib/python3.7/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)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "get_vocab.py", line 15, in <module>
    meaning = lingo.get_translations(wordstrings, source='ar', target='en')
  File "/home/shafquat/DuolingoWordList/venv/lib/python3.7/site-packages/duolingo.py", line 232, in get_translations
    raise Exception('Could not get translations')
Exception: Could not get translations

JSON Decoder Error in `get_vocabulary` Even When Logged In

Problem

Within the last week, I've begun encountering the following unexpected error when attempting to use get_vocabulary.

I've produced the following example using the current master branch:

In [1]: import duolingo

In [2]: lingo = duolingo.Duolingo("JASchilz", "FOOMYREALPASSWORD")

In [3]: lingo.get_vocabulary()
---------------------------------------------------------------------------
JSONDecodeError                           Traceback (most recent call last)

 # ...

JSONDecodeError: Expecting value: line 1 column 1 (char 0)

Details

I can confirm that I am successfully logged in, because other methods work:

In [5]: lingo.get_languages()
Out[5]:
['Esperanto',
 'Spanish',
 'Chinese',
 'Russian',
 'French',
 'German',
 'High Valyrian',
 'Japanese',
 'Korean',
 'Klingon']

I tried modifying the user agent as suggested in other issues, but it didn't help.

I have a script that uses your script, and I discovered this issue because it was reported to me by one of my users.

I can confirm that up until recently, this method did work for me and my users.

Here's the entire stack trace:

---------------------------------------------------------------------------
JSONDecodeError                           Traceback (most recent call last)
<ipython-input-4-0a68c5f0181c> in <module>
----> 1 lingo.get_vocabulary()

~\Documents\tmp\Duolingo\duolingo.py in get_vocabulary(self, language_abbr)
    457         overview_url = "https://www.duolingo.com/vocabulary/overview"
    458         overview_request = self._make_req(overview_url)
--> 459         overview = overview_request.json()
    460
    461         return overview

c:\users\jaschilz\appdata\local\programs\python\python38\lib\site-packages\requests\models.py in json(self, **kwargs)
    896                     # used.
    897                     pass
--> 898         return complexjson.loads(self.text, **kwargs)
    899
    900     @property

c:\users\jaschilz\appdata\local\programs\python\python38\lib\json\__init__.py in loads(s, cls, object_hook, parse_float, parse_int, parse_constant, object_pairs_hook, **kw)
    355             parse_int is None and parse_float is None and
    356             parse_constant is None and object_pairs_hook is None and not kw):
--> 357         return _default_decoder.decode(s)
    358     if cls is None:
    359         cls = JSONDecoder

c:\users\jaschilz\appdata\local\programs\python\python38\lib\json\decoder.py in decode(self, s, _w)
    335
    336         """
--> 337         obj, end = self.raw_decode(s, idx=_w(s, 0).end())
    338         end = _w(s, end).end()
    339         if end != len(s):

c:\users\jaschilz\appdata\local\programs\python\python38\lib\json\decoder.py in raw_decode(self, s, idx)
    353             obj, end = self.scan_once(s, idx)
    354         except StopIteration as err:
--> 355             raise JSONDecodeError("Expecting value", s, err.value) from None
    356         return obj, end

JSONDecodeError: Expecting value: line 1 column 1 (char 0)

I did a bit of debugging, and found that the get_vocabulary response from https://www.duolingo.com/vocabulary/overview is a 200 OK response with the following text:

<!DOCTYPE html><html dir="ltr"><head><title>Duolingo</title><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no"><meta name="robots" content="NOODP"><meta name="theme-color" content="#eeeeee"><noscript><meta http-equiv="refresh" content="0; url=/nojs/splash"></noscript><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black"><meta name="apple-mobile-web-app-title" content="Duolingo"><meta name="google" content="notranslate"><meta name="mobile-web-app-capable" content="yes"><meta name="apple-itunes-app" content="app-id=570060128"><link rel="apple-touch-icon" href="//d35aaqx5ub95lt.cloudfront.net/images/duolingo-touch-icon2.png"><link rel="shortcut icon" type="image/x-icon" href="//d35aaqx5ub95lt.cloudfront.net/favicon.ico"><link href="//d35aaqx5ub95lt.cloudfront.net/css/app-83ad604d.css" rel="stylesheet"> <link rel="manifest" href="/manifest.json"><meta name="twitter:site" content="@duolingo"  /><meta name="twitter:image" content="https://www.duolingo.com/images/facebook/duo200.png"  /><meta name="twitter:description" content="Learn languages by playing a game. It's 100% free, fun, and scientifically proven to work." data-react-helmet="true" /><meta name="twitter:title" content="Duolingo - Learn a language for free @duolingo"  /><meta name="twitter:card" content="summary"  /><meta property="og:url" content="https://www.duolingo.com/vocabulary/overview"  /><meta property="og:site_name" content="Duolingo"  /><meta property="og:type" content="website"  /><meta property="og:description" content="Learn languages by playing a game. It's 100% free, fun, and scientifically proven to work." data-react-helmet="true" /><meta property="og:title" content="Learn a language for free"  /><meta property="fb:app_id" content="234536436609303"  /><meta property="og:image" content="https://www.duolingo.com/images/facebook/duo200.png"  /><meta name="keywords" content="learn, spanish, german, french, portuguese, italian, english, free, lessons, course, language, study, flashcards"  /><meta name="description" content="Duolingo is the world's most popular way to learn a language. It's 100% free, fun and science-based. Practice online on duolingo.com or on the apps!" data-react-helmet="true" /><link rel="alternate" hreflang="ar" href="https://ar.duolingo.com/vocabulary/overview" /><link rel="alternate" hreflang="cs" href="https://cs.duolingo.com/vocabulary/overview" /><link rel="alternate" hreflang="de" href="https://de.duolingo.com/vocabulary/overview" /><link rel="alternate" hreflang="el" href="https://el.duolingo.com/vocabulary/overview" /><link rel="alternate" hreflang="en" href="https://www.duolingo.com/vocabulary/overview" /><link rel="alternate" hreflang="es" href="https://es.duolingo.com/vocabulary/overview" /><link rel="alternate" hreflang="fr" href="https://fr.duolingo.com/vocabulary/overview" /><link rel="alternate" hreflang="hi" href="https://hi.duolingo.com/vocabulary/overview" /><link rel="alternate" hreflang="hu" href="https://hu.duolingo.com/vocabulary/overview" /><link rel="alternate" hreflang="id" href="https://id.duolingo.com/vocabulary/overview" /><link rel="alternate" hreflang="it" href="https://it.duolingo.com/vocabulary/overview" /><link rel="alternate" hreflang="ja" href="https://ja.duolingo.com/vocabulary/overview" /><link rel="alternate" hreflang="ko" href="https://ko.duolingo.com/vocabulary/overview" /><link rel="alternate" hreflang="nl" href="https://nl-nl.duolingo.com/vocabulary/overview" /><link rel="alternate" hreflang="pl" href="https://pl.duolingo.com/vocabulary/overview" /><link rel="alternate" hreflang="pt" href="https://pt.duolingo.com/vocabulary/overview" /><link rel="alternate" hreflang="ro" href="https://ro.duolingo.com/vocabulary/overview" /><link rel="alternate" hreflang="ru" href="https://ru.duolingo.com/vocabulary/overview" /><link rel="alternate" hreflang="th" href="https://th.duolingo.com/vocabulary/overview" /><link rel="alternate" hreflang="tr" href="https://tr.duolingo.com/vocabulary/overview" /><link rel="alternate" hreflang="uk" href="https://uk.duolingo.com/vocabulary/overview" /><link rel="alternate" hreflang="vi" href="https://vi.duolingo.com/vocabulary/overview" /><link rel="alternate" hreflang="x-default" href="https://www.duolingo.com/vocabulary/overview" /><link rel="alternate" hreflang="zh" href="http://www.duolingo.cn/vocabulary/overview" /><link rel="canonical" href="https://www.duolingo.com/vocabulary/overview" id="canonical" /></head><body><noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-TX6Z97C" height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript><div id="root" onclick=""></div><script>window.duo={"detUiLanguages":["en","es","pt","it","fr","de","ja","zs","zt","ko","ru","hi","hu","tr"],"troubleshootingForumId":647,"uiLanguage":"en","oldWebUrlWhitelist":["^/comment/","^/design$","^/discussion$","^/topic/"]}</script><script>if(window.duo.version="67b44b71bc4372db285d2208f296832a5532dac1","/errors/not-supported.html"!==window.location.pathname)for(var objectEntries=("entries"in Object),features=[objectEntries],i=0;i<features.length;i++)features[i]||(window.location.href="/errors/not-supported.html")</script><script>!function(r){function e(e){for(var t,a,c=e[0],s=e[1],n=e[2],i=0,o=[];i<c.length;i++)a=c[i],Object.prototype.hasOwnProperty.call(g,a)&&g[a]&&o.push(g[a][0]),g[a]=0;for(t in s)Object.prototype.hasOwnProperty.call(s,t)&&(r[t]=s[t]);for(u&&u(e);o.length;)o.shift()();return f.push.apply(f,n||[]),d()}function d(){for(var e,t=0;t<f.length;t++){for(var a=f[t],c=!0,s=1;s<a.length;s++){var n=a[s];0!==g[n]&&(c=!1)}c&&(f.splice(t--,1),e=P(P.s=a[0]))}return e}var a={},o={0:0},g={0:0},f=[];function P(e){if(a[e])return a[e].exports;var t=a[e]={i:e,l:!1,exports:{}};return r[e].call(t.exports,t,t.exports,P),t.l=!0,t.exports}P.e=function(f){var e=[];o[f]?e.push(o[f]):0!==o[f]&&{2:1,3:1,6:1,7:1,8:1,9:1,10:1,11:1,16:1,17:1,18:1,19:1,20:1,21:1,22:1,23:1,24:1,25:1,26:1,43:1,44:1,45:1,46:1,47:1,48:1,49:1,50:1,51:1,52:1,53:1,54:1,55:1,56:1,57:1,58:1,59:1,60:1,61:1,62:1,63:1,64:1,65:1,66:1,67:1,68:1,69:1,70:1,71:1,72:1,73:1,74:1,75:1,76:1,77:1,78:1,108:1,109:1,110:1,111:1,112:1,113:1}[f]&&e.push(o[f]=new Promise(function(e,c){for(var t="rtl"===document.dir?"css/"+({1:"reactVendor.react-lazyload",2:"admin-AdminToolsPage",3:"admin-DesignGuidelinesPage",5:"commonRoutes",6:"content_partner-PartnerCheckCodePage",7:"content_partner-PartnerGetCodePage",8:"content_partner-PartnerPage",9:"content_partner-PearsonCodePage",10:"courses-CourseListPage",11:"courses-LoggedInCoursePage",12:"debug",13:"debug0",14:"debug1",15:"debug2",16:"dict-LexemeResultsContainer",17:"dict-SearchBar",18:"dict-TranslationResults",19:"dictionary",20:"home-HomePage",21:"home-Shop",22:"home-SkillTree",23:"home-VerificationPage",24:"layout-App",25:"profile-ProfilePage",26:"progress_quiz-ProgressQuizHistoryPage",34:"reactVendor.react-lifecycles-compat",39:"reactVendor.react-transition-group",43:"session-NewSentenceDiscussionPage",44:"session-SessionPage",45:"settings-AccountSettings",46:"settings-CoachSettings",47:"settings-CourseSettings",48:"settings-DeactivateSettings",49:"settings-NotificationSettings",50:"settings-PasswordSettings",51:"settings-PlusSettings",52:"settings-PrivacySettings",53:"settings-ProfileSettings",54:"settings-ResetLanguagesPage",55:"settings-SchoolsProSettings",56:"settings-SchoolsSettingsPage",57:"settings-SettingsPage",58:"settings-SoundSettings",59:"skill-ExplanationPage",60:"skill-TipsAndNotesPage",61:"static-AboutUs",62:"static-ApproachPage",63:"static-ArtCommission",64:"static-ContactPage",65:"static-DuoconPage",66:"static-ForgotPasswordPage",67:"static-GuidelinesPage",68:"static-HelpPage",69:"static-InfoPage",70:"static-JobsPage",71:"static-MobilePage",72:"static-ParticipationPage",73:"static-PlusLandingPage",74:"static-PressPage",75:"static-PrivacyPage",76:"static-ResetPasswordPage",77:"static-TeamPage",78:"static-TermsPage",108:"vocab-VocabularyPage",109:"welcome-WelcomePage"}[f]||f)+"-"+{1:"31d6cfe0",2:"fe5770cf",3:"374b1756",5:"31d6cfe0",6:"82e961f7",7:"82e961f7",8:"82e961f7",9:"82e961f7",10:"b7858790",11:"f0441238",12:"31d6cfe0",13:"31d6cfe0",14:"31d6cfe0",15:"31d6cfe0",16:"3ddb7498",17:"1292a2ec",18:"83618549",19:"0f14d773",20:"b4c34d64",21:"856cd0c8",22:"bf51c22a",23:"f3ba662a",24:"764f663f",25:"1516cf4a",26:"ca8934b7",34:"31d6cfe0",39:"31d6cfe0",43:"1116ac85",44:"abc7dd16",45:"8587fbf8",46:"0dbcc6ac",47:"b478fe7e",48:"cf4a4655",49:"93a9a3ab",50:"f66ebb58",51:"54ad04f6",52:"6cb82872",53:"b16d65a6",54:"ec374eb7",55:"44f8b153",56:"2615f771",57:"180f4822",58:"37ade699",59:"7e83ca1a",60:"914c8244",61:"972074aa",62:"4783c811",63:"cf9f5c70",64:"6e304b90",65:"50e7c460",66:"94460135",67:"94b3925d",68:"f27fbfa2",69:"ce1fcea5",70:"cae2c58d",71:"99fb5b19",72:"638030f7",73:"8f9e236c",74:"5c77240e",75:"cf9f5c70",76:"ed141fe2",77:"2d4bdfcb",78:"cf9f5c70",108:"6d4aabec",109:"fcf9c99f",110:"c6de1d2d",111:"577222a6",112:"1d38059d",113:"ccde362f",114:"31d6cfe0",115:"31d6cfe0",116:"31d6cfe0",117:"31d6cfe0",118:"31d6cfe0",119:"31d6cfe0",120:"31d6cfe0",121:"31d6cfe0",122:"31d6cfe0",123:"31d6cfe0",124:"31d6cfe0",125:"31d6cfe0",126:"31d6cfe0",127:"31d6cfe0",128:"31d6cfe0",129:"31d6cfe0",130:"31d6cfe0",131:"31d6cfe0",132:"31d6cfe0",133:"31d6cfe0",134:"31d6cfe0",135:"31d6cfe0",136:"31d6cfe0",137:"31d6cfe0",138:"31d6cfe0",139:"31d6cfe0",140:"31d6cfe0",141:"31d6cfe0",142:"31d6cfe0",143:"31d6cfe0",144:"31d6cfe0",145:"31d6cfe0",146:"31d6cfe0"}[f]+".rtl.css":"css/"+({1:"reactVendor.react-lazyload",2:"admin-AdminToolsPage",3:"admin-DesignGuidelinesPage",5:"commonRoutes",6:"content_partner-PartnerCheckCodePage",7:"content_partner-PartnerGetCodePage",8:"content_partner-PartnerPage",9:"content_partner-PearsonCodePage",10:"courses-CourseListPage",11:"courses-LoggedInCoursePage",12:"debug",13:"debug0",14:"debug1",15:"debug2",16:"dict-LexemeResultsContainer",17:"dict-SearchBar",18:"dict-TranslationResults",19:"dictionary",20:"home-HomePage",21:"home-Shop",22:"home-SkillTree",23:"home-VerificationPage",24:"layout-App",25:"profile-ProfilePage",26:"progress_quiz-ProgressQuizHistoryPage",34:"reactVendor.react-lifecycles-compat",39:"reactVendor.react-transition-group",43:"session-NewSentenceDiscussionPage",44:"session-SessionPage",45:"settings-AccountSettings",46:"settings-CoachSettings",47:"settings-CourseSettings",48:"settings-DeactivateSettings",49:"settings-NotificationSettings",50:"settings-PasswordSettings",51:"settings-PlusSettings",52:"settings-PrivacySettings",53:"settings-ProfileSettings",54:"settings-ResetLanguagesPage",55:"settings-SchoolsProSettings",56:"settings-SchoolsSettingsPage",57:"settings-SettingsPage",58:"settings-SoundSettings",59:"skill-ExplanationPage",60:"skill-TipsAndNotesPage",61:"static-AboutUs",62:"static-ApproachPage",63:"static-ArtCommission",64:"static-ContactPage",65:"static-DuoconPage",66:"static-ForgotPasswordPage",67:"static-GuidelinesPage",68:"static-HelpPage",69:"static-InfoPage",70:"static-JobsPage",71:"static-MobilePage",72:"static-ParticipationPage",73:"static-PlusLandingPage",74:"static-PressPage",75:"static-PrivacyPage",76:"static-ResetPasswordPage",77:"static-TeamPage",78:"static-TermsPage",108:"vocab-VocabularyPage",109:"welcome-WelcomePage"}[f]||f)+"-"+{1:"31d6cfe0",2:"fe5770cf",3:"374b1756",5:"31d6cfe0",6:"82e961f7",7:"82e961f7",8:"82e961f7",9:"82e961f7",10:"b7858790",11:"f0441238",12:"31d6cfe0",13:"31d6cfe0",14:"31d6cfe0",15:"31d6cfe0",16:"3ddb7498",17:"1292a2ec",18:"83618549",19:"0f14d773",20:"b4c34d64",21:"856cd0c8",22:"bf51c22a",23:"f3ba662a",24:"764f663f",25:"1516cf4a",26:"ca8934b7",34:"31d6cfe0",39:"31d6cfe0",43:"1116ac85",44:"abc7dd16",45:"8587fbf8",46:"0dbcc6ac",47:"b478fe7e",48:"cf4a4655",49:"93a9a3ab",50:"f66ebb58",51:"54ad04f6",52:"6cb82872",53:"b16d65a6",54:"ec374eb7",55:"44f8b153",56:"2615f771",57:"180f4822",58:"37ade699",59:"7e83ca1a",60:"914c8244",61:"972074aa",62:"4783c811",63:"cf9f5c70",64:"6e304b90",65:"50e7c460",66:"94460135",67:"94b3925d",68:"f27fbfa2",69:"ce1fcea5",70:"cae2c58d",71:"99fb5b19",72:"638030f7",73:"8f9e236c",74:"5c77240e",75:"cf9f5c70",76:"ed141fe2",77:"2d4bdfcb",78:"cf9f5c70",108:"6d4aabec",109:"fcf9c99f",110:"c6de1d2d",111:"577222a6",112:"1d38059d",113:"ccde362f",114:"31d6cfe0",115:"31d6cfe0",116:"31d6cfe0",117:"31d6cfe0",118:"31d6cfe0",119:"31d6cfe0",120:"31d6cfe0",121:"31d6cfe0",122:"31d6cfe0",123:"31d6cfe0",124:"31d6cfe0",125:"31d6cfe0",126:"31d6cfe0",127:"31d6cfe0",128:"31d6cfe0",129:"31d6cfe0",130:"31d6cfe0",131:"31d6cfe0",132:"31d6cfe0",133:"31d6cfe0",134:"31d6cfe0",135:"31d6cfe0",136:"31d6cfe0",137:"31d6cfe0",138:"31d6cfe0",139:"31d6cfe0",140:"31d6cfe0",141:"31d6cfe0",142:"31d6cfe0",143:"31d6cfe0",144:"31d6cfe0",145:"31d6cfe0",146:"31d6cfe0"}[f]+".css",s=P.p+t,a=document.getElementsByTagName("link"),n=0;n<a.length;n++){var i=(r=a[n]).getAttribute("data-href")||r.getAttribute("href");if("stylesheet"===r.rel&&(i===t||i===s))return e()}var o=document.getElementsByTagName("style");for(n=0;n<o.length;n++){var r;if((i=(r=o[n]).getAttribute("data-href"))===t||i===s)return e()}var d=document.createElement("link");d.rel="stylesheet",d.type="text/css",d.onload=e,d.onerror=function(e){var t=e&&e.target&&e.target.src||s,a=new Error("Loading CSS chunk "+f+" failed.\n("+t+")");a.request=t,c(a)},d.href=s,document.getElementsByTagName("head")[0].appendChild(d)}).then(function(){o[f]=0}));var a=g[f];if(0!==a)if(a)e.push(a[2]);else{var t=new Promise(function(e,t){a=g[f]=[e,t]});e.push(a[2]=t);var c,s=document.createElement("script");s.charset="utf-8",s.timeout=120,P.nc&&s.setAttribute("nonce",P.nc),s.src=P.p+"js/"+({1:"reactVendor.react-lazyload",2:"admin-AdminToolsPage",3:"admin-DesignGuidelinesPage",5:"commonRoutes",6:"content_partner-PartnerCheckCodePage",7:"content_partner-PartnerGetCodePage",8:"content_partner-PartnerPage",9:"content_partner-PearsonCodePage",10:"courses-CourseListPage",11:"courses-LoggedInCoursePage",12:"debug",13:"debug0",14:"debug1",15:"debug2",16:"dict-LexemeResultsContainer",17:"dict-SearchBar",18:"dict-TranslationResults",19:"dictionary",20:"home-HomePage",21:"home-Shop",22:"home-SkillTree",23:"home-VerificationPage",24:"layout-App",25:"profile-ProfilePage",26:"progress_quiz-ProgressQuizHistoryPage",34:"reactVendor.react-lifecycles-compat",39:"reactVendor.react-transition-group",43:"session-NewSentenceDiscussionPage",44:"session-SessionPage",45:"settings-AccountSettings",46:"settings-CoachSettings",47:"settings-CourseSettings",48:"settings-DeactivateSettings",49:"settings-NotificationSettings",50:"settings-PasswordSettings",51:"settings-PlusSettings",52:"settings-PrivacySettings",53:"settings-ProfileSettings",54:"settings-ResetLanguagesPage",55:"settings-SchoolsProSettings",56:"settings-SchoolsSettingsPage",57:"settings-SettingsPage",58:"settings-SoundSettings",59:"skill-ExplanationPage",60:"skill-TipsAndNotesPage",61:"static-AboutUs",62:"static-ApproachPage",63:"static-ArtCommission",64:"static-ContactPage",65:"static-DuoconPage",66:"static-ForgotPasswordPage",67:"static-GuidelinesPage",68:"static-HelpPage",69:"static-InfoPage",70:"static-JobsPage",71:"static-MobilePage",72:"static-ParticipationPage",73:"static-PlusLandingPage",74:"static-PressPage",75:"static-PrivacyPage",76:"static-ResetPasswordPage",77:"static-TeamPage",78:"static-TermsPage",108:"vocab-VocabularyPage",109:"welcome-WelcomePage"}[f]||f)+"-"+{1:"61c00402",2:"c8016199",3:"68e8f10d",5:"5007a2e4",6:"cfb8dfc1",7:"268b4c83",8:"5fc0436e",9:"7fb2c083",10:"26671b80",11:"95ceca6d",12:"6d02879e",13:"000a4db1",14:"acce5ba3",15:"667b5189",16:"5d598ad0",17:"4fb80433",18:"b6feda3a",19:"e498ef6c",20:"c934dae5",21:"7b89b6ad",22:"b9578907",23:"ca0ab344",24:"f44a5ae7",25:"9b0291cb",26:"493c6c99",34:"98d705b6",39:"06338fa9",43:"0ce1d0d1",44:"049546b6",45:"d40ee3a5",46:"8d02e77f",47:"0df1bcf1",48:"aacae8c8",49:"ba88a83a",50:"8104f0f7",51:"220dba9b",52:"d544968b",53:"caa12f1c",54:"488d1c5f",55:"c45d2a03",56:"5b760ef1",57:"3a93d95a",58:"94b4b7d0",59:"7b61757d",60:"2e308fbc",61:"641da908",62:"87fbca63",63:"16f1d461",64:"e763f5ce",65:"b5dd9e37",66:"9d56a47d",67:"3f59b119",68:"393e8762",69:"4cd826b7",70:"bbd12d7d",71:"f8e2accc",72:"7b9be20f",73:"a2e44b2d",74:"5a8c967b",75:"1c8a8860",76:"68bc9193",77:"52cc0208",78:"6c5d04e9",108:"f5c78fb5",109:"469a629d",110:"adadcd20",111:"62de701c",112:"f08bf8f2",113:"3a6a783c",114:"fc6c9796",115:"02dfd85d",116:"fc72e7ac",117:"e2688f86",118:"dbc30dfc",119:"224d956a",120:"c3a7cf96",121:"3ad95de2",122:"9b5e6f49",123:"79addbd6",124:"c97f8487",125:"0500078e",126:"029f1798",127:"3428c582",128:"d12859a4",129:"59f34273",130:"8cd55df6",131:"fd0f2515",132:"34c8cc96",133:"ca064bcb",134:"5c5ffde1",135:"7024bd6f",136:"91637b53",137:"48b76a4e",138:"e103a01e",139:"8394db99",140:"74c834aa",141:"f798b5a0",142:"e8c8c2db",143:"953c7ebc",144:"c3d3bc9c",145:"b7a38b50",146:"375a312c"}[f]+".js";var n=new Error;c=function(e){s.onerror=s.onload=null,clearTimeout(i);var t=g[f];if(0!==t){if(t){var a=e&&("load"===e.type?"missing":e.type),c=e&&e.target&&e.target.src;n.message="Loading chunk "+f+" failed.\n("+a+": "+c+")",n.name="ChunkLoadError",n.type=a,n.request=c,t[1](n)}g[f]=void 0}};var i=setTimeout(function(){c({type:"timeout",target:s})},12e4);s.onerror=s.onload=c,document.head.appendChild(s)}return Promise.all(e)},P.m=r,P.c=a,P.d=function(e,t,a){P.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:a})},P.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},P.t=function(t,e){if(1&e&&(t=P(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var a=Object.create(null);if(P.r(a),Object.defineProperty(a,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var c in t)P.d(a,c,function(e){return t[e]}.bind(null,c));return a},P.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return P.d(t,"a",t),t},P.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},P.p="//d35aaqx5ub95lt.cloudfront.net/",P.oe=function(e){throw console.error(e),e};var t=window.webpackJsonp=window.webpackJsonp||[],c=t.push.bind(t);t.push=e,t=t.slice();for(var s=0;s<t.length;s++)e(t[s]);var u=c;d()}([])</script><script src="//d35aaqx5ub95lt.cloudfront.net/js/reactVendor.react-router-fc6dcf1f.js"></script><script src="//d35aaqx5ub95lt.cloudfront.net/js/reactVendor.react-redux-72d3aa1c.js"></script><script src="//d35aaqx5ub95lt.cloudfront.net/js/reactVendor.react-focus-lock-a1749003.js"></script><script src="//d35aaqx5ub95lt.cloudfront.net/js/reactVendor.react-dom-3e55b92e.js"></script><script src="//d35aaqx5ub95lt.cloudfront.net/js/reactVendor.react-popper-2a2497bf.js"></script><script src="//d35aaqx5ub95lt.cloudfront.net/js/reactVendor.react-helmet-3b40158e.js"></script><script src="//d35aaqx5ub95lt.cloudfront.net/js/reactVendor.react-is-5ffc0b39.js"></script><script src="//d35aaqx5ub95lt.cloudfront.net/js/reactVendor.react-8da796d3.js"></script><script src="//d35aaqx5ub95lt.cloudfront.net/js/reactVendor.react-clientside-effect-8d11965a.js"></script><script src="//d35aaqx5ub95lt.cloudfront.net/js/reactVendor.react-fast-compare-1caf2fa2.js"></script><script src="//d35aaqx5ub95lt.cloudfront.net/js/reactVendor.react-side-effect-d2e2570d.js"></script><script src="//d35aaqx5ub95lt.cloudfront.net/js/reactVendor.redux-logger-a26d45c5.js"></script><script src="//d35aaqx5ub95lt.cloudfront.net/js/reactVendor.redux-thunk-88bd17e7.js"></script><script src="//d35aaqx5ub95lt.cloudfront.net/js/reactVendor.redux-949db9a1.js"></script>     <script src="//d35aaqx5ub95lt.cloudfront.net/js/strings/en-c451001a.js"></script>                       <script src="//d35aaqx5ub95lt.cloudfront.net/js/app-fc28c5f9.js"></script><noscript><div style="position:fixed; top:0; left:0; display:none" width="1" height="1"><img src="//collector-PXAsQJFZ9W.perimeterx.net/api/v1/collector/noScript.gif?appId=PXAsQJFZ9W" alt="PxPixel"></div></noscript></body></html>

Incidentally, when I visit https://www.duolingo.com/vocabulary/overview in my browser I get a JSON list of vocabulary.

Thank You!

Thanks for looking into this!

issue with get_known_words

So I was trying to use duolingo.py, but suddenly get_known_words() stopped working for some reason. It was working earlier, and then suddenly python started throwing me a KeyError. I can't recall changing anything. I created a Duolingo object called lingo, passing in the username and password. Then was the problem line:

print(lingo.get_known_words('fr'))

Here is the error message, which by the way appeared with every language I tried (i.e., several different languages I am learning):

Traceback (most recent call last): File "/Users/cat/PycharmProjects/duolingo/duolingo_proj.py", line 6, in <module> print(lingo.get_known_words('fr')) File "/anaconda/lib/python3.5/duolingo.py", line 341, in get_known_words for topic in self.user_data.language_data[lang]['skills']: KeyError: 'fr'

Am I typing something wrong, or did the function just stop working?

Edit: noticed that on the website itself I was having trouble seeing wordlists on some languages, incidentally the same ones I tried when noticing the python error. get_known_words() worked just fine with German.

how did you get vocabulary at first?

i am developing you code to android and what is wonder for me is after make _login, self.vocabulary is filled. but i don't know how.
can you explain me more? i am new at python!
and what do you send as session?

JSON Extra data error on initial request

Could it be that the object has multiple top level objects and is bad JSON? I'm able to visit https://www.duolingo.com/users/ with my browser and get a JSON object back there with my data... and I pasted it in a json pretty printer and it seems fine there...

lingo  = duolingo.Duolingo('<username>')
  File "/usr/local/lib/python3.7/site-packages/duolingo.py", line 36, in __init__
    self.user_data = Struct(**self._get_data())
  File "/usr/local/lib/python3.7/site-packages/duolingo.py", line 177, in _get_data
    get = self._make_req(self.user_url).json()
  File "/usr/local/lib/python3.7/site-packages/requests/models.py", line 897, in json
    return complexjson.loads(self.text, **kwargs)
  File "/usr/local/Cellar/python/3.7.6_1/Frameworks/Python.framework/Versions/3.7/lib/python3.7/json/__init__.py", line 348, in loads
    return _default_decoder.decode(s)
  File "/usr/local/Cellar/python/3.7.6_1/Frameworks/Python.framework/Versions/3.7/lib/python3.7/json/decoder.py", line 340, in decode
    raise JSONDecodeError("Extra data", s, end)
json.decoder.JSONDecodeError: Extra data: line 1 column 5 (char 4)

get_vocabulary method doesn't work

get_vocabulary method throws a JSON error like the following:

Traceback (most recent call last):
File "C:/Users/asus/.PyCharmCE2017.3/config/scratches/duolingo_auth.py", line 9, in
print(lingo.get_vocabulary(language_abbr="es"))
File "C:\Users\asus\AppData\Local\Programs\Python\Python36-32\lib\site-packages\duolingo.py", line 240, in get_vocabulary
overview = overview_request.json()
File "C:\Users\asus\AppData\Local\Programs\Python\Python36-32\lib\site-packages\requests\models.py", line 892, in json
return complexjson.loads(self.text, **kwargs)
File "C:\Users\asus\AppData\Local\Programs\Python\Python36-32\lib\json_init_.py", line 354, in loads
return _default_decoder.decode(s)
File "C:\Users\asus\AppData\Local\Programs\Python\Python36-32\lib\json\decoder.py", line 339, in decode
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
File "C:\Users\asus\AppData\Local\Programs\Python\Python36-32\lib\json\decoder.py", line 357, in raw_decode
raise JSONDecodeError("Expecting value", s, err.value) from None

Several methods are not working...

The one I am most interested in is get_audio_url.

>>> u.get_audio_url('hello', language_abbr='en')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/addohm/anaconda3/envs/DuolingoUpdate/lib/python3.7/site-packages/duolingo.py", line 300, in get_audio_url
    tts_voice = self._get_voice(language_abbr, rand=random, voice=voice)
  File "/home/addohm/anaconda3/envs/DuolingoUpdate/lib/python3.7/site-packages/duolingo.py", line 276, in _get_voice
    self._process_tts_voices()
  File "/home/addohm/anaconda3/envs/DuolingoUpdate/lib/python3.7/site-packages/duolingo.py", line 269, in _process_tts_voices
    voices_js = re.search('duo\.tts_multi_voices = {.+};', self._homepage).group(0)
AttributeError: 'NoneType' object has no attribute 'group'

Others include:

>>> u.get_translations(['de', 'du'])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/addohm/anaconda3/envs/DuolingoUpdate/lib/python3.7/site-packages/duolingo.py", line 222, in get_translations
    target = self.user_data.language_data.keys()[0]
TypeError: 'dict_keys' object does not support indexing
>>> u.get_vocabulary()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/addohm/anaconda3/envs/DuolingoUpdate/lib/python3.7/site-packages/duolingo.py", line 240, in get_vocabulary
    overview = overview_request.json()
  File "/home/addohm/anaconda3/envs/DuolingoUpdate/lib/python3.7/site-packages/requests/models.py", line 897, in json
    return complexjson.loads(self.text, **kwargs)
  File "/home/addohm/anaconda3/envs/DuolingoUpdate/lib/python3.7/site-packages/simplejson/__init__.py", line 518, in loads
    return _default_decoder.decode(s)
  File "/home/addohm/anaconda3/envs/DuolingoUpdate/lib/python3.7/site-packages/simplejson/decoder.py", line 370, in decode
    obj, end = self.raw_decode(s)
  File "/home/addohm/anaconda3/envs/DuolingoUpdate/lib/python3.7/site-packages/simplejson/decoder.py", line 400, in raw_decode
    return self.scan_once(s, idx=_w(s, idx).end())
simplejson.errors.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

Also, you may want to be specific about the requirements in the readme.md. What python version is required here? Is that perhaps where my issues are coming from? Or is it perhaps that Duolingo has changed their API\JSON?

pip install not working

This is the trace I get, maybe im missing something ?
$ sudo pip install duolingo-api
Downloading/unpacking duolingo-api
Could not find any downloads that satisfy the requirement duolingo-api
Cleaning up...
No distributions at all found for duolingo-api

$ sudo pip install duolingo-api 0.2
Downloading/unpacking duolingo-api
Could not find any downloads that satisfy the requirement duolingo-api
Cleaning up...
No distributions at all found for duolingo-api

Doesn't work with requests==2.10.0

If the requests module version is 2.10.0, Duolingo returns status code 429 (Too many requests) every time. Due to this, on initialising the class Duolingo object, the following line in _login function:

attempt = self._make_req(login_url, data).json()

throws the following error

ValueError: No JSON object could be decoded

On debugging, the reason seems to be the following request header value:

'User-Agent': 'python-requests/2.10.0'

I've checked this on multiple machines (macOS Sierra and Ubuntu 14.04) on python2.7 and python3.5.2. Changing the request header to anything except this particular value works fine. The other user agent values I checked are:

  • python-requests/2.9.2
  • python-requests/2.11.0
  • python-requests/2.13.0
  • Mozilla
  • Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1664.3 Safari/537.36

A possible fix is to hardcode the User-Agent header value to one of the above values maybe? I can raise a pull request if this fix seems reasonable.

XP today is not reliable

I'm looking at two accounts, and see that "xp_today" is still set the next day, even though "streak_extended_today" is false, and it's already a new day.

Account 1:

lingo.get_streak_info()
{'daily_goal': 20, 'site_streak': xxx, 'streak_extended_today': False}

lingo.get_daily_xp_progress()
{   'lessons_today': [...],
    'xp_goal': 20,
    'xp_today': 94}

Account 2:

lingo.get_streak_info()
{'daily_goal': 10, 'site_streak': xxx, 'streak_extended_today': True}

lingo.get_daily_xp_progress()
{'lessons_today': [], 'xp_goal': 10, 'xp_today': 0}

Account 1 didn't do any lessons today, but the "lessons_today" list is populated, "xp_today" is set. However "streak_extended_today" is not set.

Account 2 did lessons today, however "xp_today" is 0, but "streak_extended_today" is set.

I'm aware that this is data coming from the Duolingo server, but I find that unreliable.
Is there a good way to get today's data and status?

"DuolingoException: Login failed"

Dear Team.
I'm using this nice piece of code to check my duolingo points each day :)
I have not change anything, however since some few hours I am getting this.
DuolingoException: Login failed
Any ideas?

Thanks.
Omar C.

Duolingo API

Hi! How have you been? What this api is about? This api is supposed to build the entire duolingo web?

how to - mini script: auto buy streak freeze

here mini script to check few times a day (see crontab schedule) and try to buy streak freeze.
For your off-line vacation :-)

Not tried for multiply languages (if you learn more than one)
was a quick hack, please feel free to adapt or add to wiki here.
I run it on my Pi which has kodi via osmc installed.

#######Start of script
##redir in cron job 
## 0 2,8,14,20 * * * python /home/osmc/MyScript/DuolingoVacation.py >> /home/osmc/MyScript/log/DuoVac_$(date +\%Y\%m\%d).log

import logging
import sys
import duolingo

ScriptName = 'Duolingo Vacation'
USER='MyUserName here'

logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s',level=logging.INFO,stream=sys.stdout)
logging.info('=========Start Script: ' + (ScriptName))

#######Login/pick user. 
#lingo = duolingo.Duolingo(USER)
lingo  = duolingo.Duolingo(USER, password='My password here')
logging.info('Logon as: ' + (USER))


#######Simple stuff Lang
Mylanguages = lingo.get_languages(abbreviations=False)
logging.info('Languages: ' + str(Mylanguages))


#######Simple stuff Get streak info
streak_info = lingo.get_streak_info()
logging.info('streak_info: ' + str(streak_info))

#######Buy Freeze
BuyStreak = lingo.buy_streak_freeze()
logging.info('streak_buy: ' + str(BuyStreak))


logging.info('=========End Script: ' + (ScriptName))

Example log output of the above code.

  • Didn't reach daily goal on day before so it bought at 2am during first run of the next day
  • At lunch was reaching my daily goal for the day ('streak_extended_today': True)
2018-03-05 02:00:01,832 INFO =========Start Script: Duolingo Vacation
2018-03-05 02:00:07,391 INFO Logon as: MyUserName here
2018-03-05 02:00:07,392 INFO Languages: [u'Spanish']
2018-03-05 02:00:07,398 INFO streak_info: {'site_streak': 733, 'daily_goal': 50, 'streak_extended_today': False}
2018-03-05 02:00:07,717 INFO streak_buy: True
2018-03-05 02:00:07,718 INFO =========End Script: Duolingo Vacation
2018-03-05 08:00:02,253 INFO =========Start Script: Duolingo Vacation
2018-03-05 08:00:05,936 INFO Logon as: MyUserName here
2018-03-05 08:00:05,937 INFO Languages: [u'Spanish']
2018-03-05 08:00:05,937 INFO streak_info: {'site_streak': 733, 'daily_goal': 50, 'streak_extended_today': False}
2018-03-05 08:00:06,062 INFO streak_buy: False
2018-03-05 08:00:06,062 INFO =========End Script: Duolingo Vacation
2018-03-05 14:00:02,104 INFO =========Start Script: Duolingo Vacation
2018-03-05 14:00:05,832 INFO Logon as: MyUserName here
2018-03-05 14:00:05,833 INFO Languages: [u'Spanish']
2018-03-05 14:00:05,834 INFO streak_info: {'site_streak': 734, 'daily_goal': 50, 'streak_extended_today': True}
2018-03-05 14:00:05,954 INFO streak_buy: False
2018-03-05 14:00:05,954 INFO =========End Script: Duolingo Vacation
2018-03-05 20:00:01,981 INFO =========Start Script: Duolingo Vacation
2018-03-05 20:00:05,312 INFO Logon as: MyUserName here
2018-03-05 20:00:05,313 INFO Languages: [u'Spanish']
2018-03-05 20:00:05,313 INFO streak_info: {'site_streak': 734, 'daily_goal': 50, 'streak_extended_today': True}
2018-03-05 20:00:05,437 INFO streak_buy: False
2018-03-05 20:00:05,437 INFO =========End Script: Duolingo Vacation

`get_daily_xp_progress` only fetch the last 300 lessons

The method get_daily_xp_progress only fetch the last 300 lessons. Is it possible to retrieve more than that? I have looked through your source code, and it doesn't seem to have any timestamp arguments.

Is there some kind of pointers in the response from the duolingo endpoints we could roll / scroll on? 🤔

Proposal: Add method to retrieve word's dictionary entry

Duolingo exposes /api/1/dictionary_page?lexeme_id that will return the dictionary entry for the given lexeme_id. Sample response below:

{
    "alternative_forms": [
        {
            "case": null,
            "gender": null,
            "word": "जूलिया",
            "tts": "https://d1vq87e9lcf771.cloudfront.net/aditi/96ee15d0b8ad10ffbbdadf249d6089e1",
            "text": "यह जूलिया है।",
            "discussion": null,
            "number": null,
            "invalid": false,
            "highlighted": true,
            "translation_text": "This is Julia.",
            "link": "/dictionary/Hindi/%E0%A4%9C%E0%A5%82%E0%A4%B2%E0%A4%BF%E0%A4%AF%E0%A4%BE/eae018a989bee60a236427f4883015c2",
            "example_sentence": "यह जूलिया है।",
            "word_value_matched": true,
            "translation": "This is <span class=\"highlighted\">Julia</span>."
        }
    ],
    "has_tts": true,
    "word": "जूलिया",
    "language_information": {},
    "from_language_name": "English",
    "tts": "https://d1vq87e9lcf771.cloudfront.net/aditi/0aeea139ce8205bb4f38e88130b97bac",
    "learning_language": "hi",
    "translations": "Julia (female name)",
    "learning_language_name": "Hindi",
    "pos": null,
    "from_language": "en",
    "is_generic": false,
    "lexeme_id": "eae018a989bee60a236427f4883015c2",
    "canonical_path": "/dictionary/Hindi/%E0%A4%9C%E0%A5%82%E0%A4%B2%E0%A4%BF%E0%A4%AF%E0%A4%BE/eae018a989bee60a236427f4883015c2"
}

I propose adding get_word_definition_by_id(id) or some such method to make this request.

get_language_from_abbr() does not recognize ui_language

When using get_language_from_abbr() to get the full string of the ui_language in user_data None will be returned. This is because the ui_language is not added the languages lists that get_language_from_abbr() uses. I think this would be solved by making the lookup from get_language_from_abbr and get_abbreviation_of be based on a stored list of language-abbreviation pairs.

Besides, I think returning None when failing to find the abbreviation is not really expected behavior and it would be better if an exception was raised. Raising that exception should be easy and I can make a PR if you want.

How to use this for translation from English to Portuguese ?

I am trying to see, if this can be used to translate an english text to another language.

lingo.get_translations(['hello', 'welcome'], source='en', target='pt')
{u'welcome': [], u'hello': []}

I would expect a response like

{u'hello':[Olá], u'welcome':[bem-vindo]}

Is there anything I need to do ? Sorry, if I misunderstood the API capabilities...

This only works for the current language

I've been toying around with this, and I noticed that if I did the following:

lingo  = duolingo.Duolingo('JonRussell2')
print lingo.get_language_progress('pt')
print lingo.get_language_progress('de')

It would produce the following:

C:\Workspaces\python sandbox>python duo.py
{'streak': 49, 'language_string': u'Portuguese', 'level_progress': 717, 'level_percent': 65, 'language': u'pt', 'points_rank': 3, 'level_points': 1100, 'next_level': 14, 'points': 5617, 'num_skills_learned':
40, 'level_left': 383}
Traceback (most recent call last):
File "duo.py", line 46, in
print lingo.get_language_progress('de');
File "C:\Python27\lib\site-packages\duolingo__init__.py", line 111, in get_language_progress
return self._make_dict(fields, self.user_data.language_data[lang])
KeyError: 'de'

Apparently, this "get_language_progress" only gets the progress if we're logged into that language. Once I log into german, it no longer gets portuguese. It's evidently trying to parse the language JSON returned when we load the page, but that only returns one language at a time.

Since this function only gets the current language anyway, why should this take in a language as an input? Instead of this:

def get_language_progress(self, lang):
        fields = ['streak', 'language_string', 'level_progress', 'num_skills_learned',
                'level_percent', 'level_points', 'points_rank', 'next_level',
                'level_left', 'language', 'points']
        return self._make_dict(fields, self.user_data.language_data[lang])

Why not this:

def get_language_progress(self):
        language = duolingo.user_data.language_data.keys()[0]
        fields = ['streak', 'language_string', 'level_progress', 'num_skills_learned',
                'level_percent', 'level_points', 'points_rank', 'next_level',
                'level_left', 'language', 'points']
        return self._make_dict(fields, self.user_data.language_data[language])

I've tested that, and I think it works pretty well. Alternatively, is there a way to get multiple languages' data at once?

get_activity_stream fails when logged in

---------------------------------------------------------------------------
JSONDecodeError                           Traceback (most recent call last)
~/virtualenvs/p3/lib/python3.5/site-packages/duolingo.py in get_activity_stream(self, before)
     43         try:
---> 44             return request.json()
     45         except:

~/virtualenvs/p3/lib/python3.5/site-packages/requests/models.py in json(self, **kwargs)
    891                     pass
--> 892         return complexjson.loads(self.text, **kwargs)
    893 

/usr/lib/python3.5/json/__init__.py in loads(s, encoding, cls, object_hook, parse_float, parse_int, parse_constant, object_pairs_hook, **kw)
    318             parse_constant is None and object_pairs_hook is None and not kw):
--> 319         return _default_decoder.decode(s)
    320     if cls is None:

/usr/lib/python3.5/json/decoder.py in decode(self, s, _w)
    338         """
--> 339         obj, end = self.raw_decode(s, idx=_w(s, 0).end())
    340         end = _w(s, end).end()

/usr/lib/python3.5/json/decoder.py in raw_decode(self, s, idx)
    356         except StopIteration as err:
--> 357             raise JSONDecodeError("Expecting value", s, err.value) from None
    358         return obj, end

JSONDecodeError: Expecting value: line 1 column 1 (char 0)

During handling of the above exception, another exception occurred:

Exception                                 Traceback (most recent call last)
<ipython-input-34-70c4f0c04c1c> in <module>()
----> 1 lingo.get_activity_stream()

~/virtualenvs/p3/lib/python3.5/site-packages/duolingo.py in get_activity_stream(self, before)
     44             return request.json()
     45         except:
---> 46             raise Exception('Could not get activity stream')
     47 
     48     def _switch_language(self, lang):

Exception: Could not get activity stream

No support for Leagues

I don't see anything in here for Leagues ("leaderboards"). The data seems to come from duolingo-leaderboards-prod.duolingo.com/leaderboards/<uuid>/users/<userid>
I'm happy to write some code for this, but am I just missing it? Any idea where the UUID comes from?

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.