Solution: Known Word -> New Word

OVERVIEW
→ This is a solution to undo changes made by clicking the finish lesson / paging moves to known.
→ Words that are marked as ‘known’ will be set back as ‘new’ blue words.
→ This undo does not apply to words that have been LinGQed and set to known.

HOW TO DO IT

  1. Open the lesson you need to fix
  2. get the LessonId and Language code from the URL
    LessonID and Lanmguage
  3. Open Console (F12 on windows)
  4. Change the language and LessonId at the top of the script with the ones in your URL and paste the below code into the console.
  5. Leave tab opens while the process resolves (~2 seconds per word)
  6. Refresh page when complete.

UPDATED SCRIPT 27/2/2024

//Rooster Known to New Words
//This script will make words 'blue' again if marked known by turning page or finishing the lesson.

let language = 'en'; // Replace 'en' with the relevant language code
let lessonId = 26237371; // Replace 26237371 with the relevant lessonId

async function postCard(term, language, lessonId) {
    const jsonData = {
        "term": term,
        "status": 0,
        "content": lessonId
    };

    return fetch('https://www.lingq.com/api/v2/' + language + '/cards/', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(jsonData)
    }).then((response) => {
        console.log(response);
        if (!response.ok) {
            throw new Error('Failed to post card.');
        }
        return response.json();
    });
}

async function updateCardStatus(cardId, language) {
    const jsonData = {
      "status": -1,
      "extended_status": 0
    };
  
    return fetch(`https://www.lingq.com/api/v2/${language}/cards/${cardId}/`, {
      method: 'PATCH',
      headers: {
          'Content-Type': 'application/json'
      },
      body: JSON.stringify(jsonData)
    }).then((response) => {
      console.log('Updated card status', response);
      if (!response.ok) {
          throw new Error('Failed to update card status.');
      }
      return response.json();
    });
  }

async function processLessonWords(headers) {
    const response = await fetch(`https://www.lingq.com/api/v3/${language}/lessons/${lessonId}/words/?cardsTranslitFormat=list`);
    const data = await response.json();

    for (const wordObj of Object.values(data.words)) {
        if (wordObj.status === "known") {
            try {
                const postResponse = await postCard(wordObj.text, language, lessonId);
                const cardId = postResponse.pk; // Assuming 'pk' is in the response

                await delay(1000); // I1-second delay after posting

                //await updateCardStatus(cardId, language); // Update card status before deleting

                //await delay(2000); // 1-second delay after patching -> this fixes the UI errors

                await deleteCard(cardId, language, headers);

                await delay(1000); // 1-second delay after deleting
            } catch (error) {
                console.error('Error in processing word:', wordObj.text, error);
            }
        }
    }
}

async function deleteCard(cardId, language, headers) {
    return fetch(`https://www.lingq.com/api/v2/${language}/cards/${cardId}/`, {
        method: 'DELETE',
        headers: headers
    }).then((response) => {
        console.log('Deleted card', response);
        if (!response.ok) {
            throw new Error('Failed to delete card.');
        }
    });
}


//to prevent too many requests from LingQ
function delay(time) {
    return new Promise(resolve => setTimeout(resolve, time));
}


async function main() {
    await processLessonWords();
}

main();

17 Likes

Oooo
Thanks a Lot!
Will try when Get to my PC

1 Like

It works !! Removes known words from the lesson that don’t have translation in card

1 Like

Worked for me… But beware, if you’re starting out in Lingq and know some words already, you will want to manually flag them immediately as ‘Known’ … But I think this fix will also return these words to blue (because for these words you skipped intermediate stages 1,2,3, etc) , which in my case was not wanted as I had to review all again and re-flag these words back to ‘known’.

1 Like

This is a limitation. You’re correct.

In future you can use this modification to just reset the words you want.

1 Like

If you’re willing to install an addon called Tampermonkey, I have a script that adds two buttons to the overview page to either reset to unknown or change all to new.

Notes:

  • your lesson will still think you’ve marked those words as known, so the overview of “unknown words” will still show as 0.
  • it does change total known words for your language
1 Like

I did this on a lesson. Some things to note for those curious:

  1. On Firefox, I first had to disable ‘paste protection’ for the Developer Tools Console. I did this by going to about: config, then searching devtools.selfxss.count and setting it to 100. After I finished running the script, I put ‘paste protection’ back on for security reasons.
  2. It took about ~20 minutes to run.
  3. It successfully removed the words sent to Known without a selected definition. It appears to have triggered the stats to consider those as removed as new ‘lingQs’, so in my case, I had -266 Known Words and +266 lingQs. You won’t find those words in your vocabulary list though.

immagine

  1. Afterwards, on Android, I went through the in-lesson > Vocabulary > New Words list and marked some words back as Known. I did this on Android, because on the browser the in-lesson vocabulary lists are not complete (a bug, which has been around for ages on the browser).

Honestly, it’s just ridiculous that LingQ adamanatly continues to refuse to allow an option to turn off ‘Complete lesson moves New Words to Known’, despite having requests to do so for years. What a waste a time.

3 Likes

Pretty complicated, isn’t it? You could also simply go back to the lesson and manually mark the words you don’t know as “new”. Then you don’t have to tinker with the source code and there’s no risk of messing everything up with a typo.

2 Likes

Thanks for the graphic there @nfera

It seems that calling delete on the word isnt removing the LingQ from the UI. It needs to be set to -1 and then deleted. I’ve updated the above script to fix the UI issue going forward.

Edit: → Appears that setting status to -1 causes Delete to fail. Not sure atm.

I think an undo button would do nicely

Unless i’m mistaken, there is no way to mark words as new again. Except via method above. You’re right that the solution is complicated though.

2 Likes

Trying to maintain an accurate Known Word count I think can be somewhat toxic, like maintaining your streak, as others mentioned the other day. With our Known Word counts, we are really fighting an uphill battle against LingQ, who actively tries to force on us how we should learn (lingQ every word, otherwise it should be marked as Known!).

1 Like

Man, I can not but admire your inventivity. Fortunately I don’t need it currently, but you never know when a rabbit bytes the cow. Kudo’s!

1 Like

Great initiative, lots of people struggle with this LingQ quirk.

It took about ~20 minutes to run.

This is pretty slow. If I understand the code correctly, one network request is made per word (card). This seems inefficient, I would try to batch the words together and minimize the number of requests to the server.
I’ve given it a try in Python and putting the cards in a dictionary works. Although I don’t see a way to delete cards entirely, only ignore. Also I don’t know how many cards can be batched together, the web app allows 200. So it might be necessary to split larger numbers of cards into multiple batches.
Maybe this can give some ideas? Feel free to disregard if I misunderstood something and this cannot be applied here.

Summary
import json
import requests

BASE_URL = "https://www.lingq.com"
KEY = "1234"


def change_cards_status(language_code, status, cards):
    url = f"{BASE_URL}/api/v3/{language_code}/cards/change_status/"

    data = {"cards": cards, "status": status}

    headers = {
        "Content-Type": "application/json",
        "Accept": "application/json",
        "Accept-Encoding": "gzip, deflate, br",
        "Authorization": f"Token {KEY}",
    }

    response = requests.post(url, headers=headers, data=json.dumps(data))

    if response.ok:
        print("Request successful")
        print("Response:", response.json())
    else:
        print("Request failed with status code:", response.status_code)
        print("Response content:", response.text)


status = "ignored"
cards = [507361485, 456657328]
language_code = "is"
change_cards_status(language_code, status, cards)
2 Likes

That end point looks great for mass updates of existing LingQs. Not sure it applies to this case though. If a Word not LingQ goes to Known it has a different ID structure than LingQed → Known. There might be an endpoint or payload to delete it from known word list without LingQing it, I tried a bunch of things and was unsuccessful.

3 Likes

Ah, I see the words that are “paged to known” or ignored don’t even appear on the vocabulary list for some reason. So you have to remember their id while still in the lesson. Got it. And the change_status API doesn’t even work on those words it seems.
Tbh. I didn’t even know you could make words blue again. This whole affair seems pretty gnarly…

2 Likes

This code will get all word Ids in a lesson that are not LingQed, have to filter out ignored and new though. Couldn’t find a way to post delete though, was trying on the knownWords v1 endpoint.

let knownWords = {};
 async function fetchKnownWords(languageCode, lessonIds) {
      for (const lessonId of lessonIds) {
          // First API call for known words and cards
          let response = await fetch(`https://www.lingq.com/api/v3/${languageCode}/lessons/${lessonId}/words/?cardsTranslitFormat=list`);
          let data = await response.json();
  
          for (let wordId in data.words) {
              const word = data.words[wordId];
              knownWords[word.text] = {
                  ...word,
                  wordID: wordId,
                  type: "word",
                  language: languageCode,
                  lessonId: lessonId
              };
3 Likes

Oh yes. There is. You can reset the word to level 1. Of course it will not be marked blue, but yellow. So what? It’s virtually the same. Unknown is unknown, regardless of whether the color is blue or intense yellow.

1 Like

From the perspective of words that aren’t separated (e.g., Japanese), marking them as “1” won’t solve my problems that come with those languages. Right now, I have to fix many sentences to have words separated as I want them and/or to be the intended words. Sometimes, I just leave them as is because I’m not in the mood to change them, especially for the millionth time for the same transcribed / parsing error. It’s my intention to just leave the “lessons” as incomplete, but sometimes I’ll hit the last “next” and finish the lesson. Thus, I get junk words that I don’t want to track if I mark them as “1”.

1 Like