thags / lingqAnkiSync

Sync Lingq words with Anki
8 stars 1 forks source link

issue when installing #23

Closed pierreg14 closed 1 year ago

pierreg14 commented 1 year ago

Hi, when I try to install the addon and I restart it, it shows me this message :

"Traceback (most recent call last): File "aqt/addons.py", line 230, in loadAddons File "/Users/pierreg/Library/Application Support/Anki2/addons21/594879777/init.py", line 1, in from .popUpWindow import UI File "/Users/pierreg/Library/Application Support/Anki2/addons21/594879777/popUpWindow.py", line 6, in from .UIActionHandler import ActionHandler File "/Users/pierreg/Library/Application Support/Anki2/addons21/594879777/UIActionHandler.py", line 1, in from .Converter import ConvertAnkiCardsToLingqs, ConvertLingqsToAnkiCards File "/Users/pierreg/Library/Application Support/Anki2/addons21/594879777/Converter.py", line 7, in def ConvertAnkiCardsToLingqs(ankiCards: list[AnkiCard], statusToInterval: dict[int:int]) -> list[Lingq]: TypeError: 'type' object is not subscriptable"

mac OS Big sur 11.7.4 Anki version : 2.1.49 Python 3.8.6 Qt 5.14.2 PyQt 5.14.2

On the anki addon link --> Supported Anki versions: 2.1.6-2.1.16+ (Updated 2023-03-20)

Thanks.

pierreg14 commented 1 year ago

A friend who knows py told me that my python doesn't support "list" or something like that. I fixed my issue with this! Hope it will help

LingqApi.py

from typing import List
import requests
from .Models.Lingq import Lingq
class LingqApi:
    def __init__(self, apiKey, languageCode):
        self.apiKey = apiKey
        self.languageCode = languageCode
        self._baseUrl = f"https://www.lingq.com/api/v2/{languageCode}/cards"
        self.unformatedLingqs = []
        self.lingqs = []

    def GetAllLingqs(self) -> List[Lingq]:
        nextUrl = self._baseUrl
        while (nextUrl != None):
            words_response = self._GetSinglePage(nextUrl)
            words = words_response.json()['results']
            self.unformatedLingqs.extend(words)
            nextUrl = words_response.json()['next']
        self._ConvertApiToLingqs()
        return self.lingqs

    def _GetSinglePage(self, url):
        headers = {'Authorization': f'Token {self.apiKey}'}
        words_response = requests.get(url, headers=headers)
        words_response.raise_for_status()
        return words_response

    def _ConvertApiToLingqs(self) -> List[Lingq]:
        for lingq in self.unformatedLingqs:
            self.lingqs.append(
                Lingq(
                    lingq['pk'],
                    lingq['term'],
                    lingq['hints'][0]['text']
                        if (len(lingq['hints']) > 0)
                        else " ",
                    lingq['status'],
                    lingq['extended_status']
            ))

    def SyncStatusesToLingq(self, lingqs: List[Lingq]) -> int:
        lingqsUpdated = 0
        for lingq in lingqs:
            lingq = self._GetLingqStatusReadyForSync(lingq)
            if (self._ShouldUpdateStatus(lingq.primaryKey, lingq.status) == False): continue
            headers = {"Authorization": f"Token {self.apiKey}"}
            url = f"{self._baseUrl}/{lingq.primaryKey}/"
            response = requests.patch(url, headers=headers, data={
                "status": lingq.status, "extended_status": lingq.extended_status})
            response.raise_for_status()
            lingqsUpdated += 1
        return lingqsUpdated

    def _GetLingqStatus(self, lingqPrimaryKey):
        url = f"{self._baseUrl}/{lingqPrimaryKey}/"
        response = self._GetSinglePage(url)
        status = response.json()['status']
        extendedStatus = response.json()['extended_status']
        if (extendedStatus == 3 and status == 3):
            status = 4
        return status

    def _GetLingqStatusReadyForSync(self, lingq: Lingq):
        if (lingq.status == 4):
            lingq.extended_status = 3
            lingq.status = 3
        else:
            lingq.extended_status = 0

        return lingq

    def _ShouldUpdateStatus(self, lingqPrimaryKey, newStatus) -> bool:
        lingqCurrentStatus = self._GetLingqStatus(lingqPrimaryKey)
        return int(lingqCurrentStatus) < int(newStatus)

AnkiHandler.py

from typing import List
from aqt import mw
from anki.notes import Note
from anki.cards import Card
from .Models.AnkiCard import AnkiCard

def CreateNotesFromCards(cards: List[AnkiCard], deckName: str) -> int:
    return sum(CreateNote(card, deckName) == True for card in cards)

def CreateNote(card: AnkiCard, deckName: str) -> bool:
    if (DoesDuplicateCardExistInDeck(card.primaryKey , deckName)):
        return False
    modelName = "LingqAnkiSync"
    noteFields = ["Front", "Back", "LingqPK"]
    CreateNoteTypeIfNotExist(modelName, noteFields, deckName)

    model = mw.col.models.byName(modelName)
    note = Note(mw.col, model)

    note["Front"] = card.word
    note["Back"] = card.translation
    note["LingqPK"] = str(card.primaryKey)

    deck_id = mw.col.decks.id(deckName)
    note.model()['did'] = deck_id
    mw.col.addNote(note)
    mw.col.sched.set_due_date([note.id], str(card.interval))
    return True

def DoesDuplicateCardExistInDeck(LingqPK, deckName):
    return len(mw.col.find_cards(f'deck:"{deckName}" LingqPK:"{LingqPK}"')) > 0

def CreateNoteType(name: str, fields: List):
    model = mw.col.models.new(name)

    for field in fields:
        mw.col.models.addField(model, mw.col.models.newField(field))

    template = mw.col.models.newTemplate("lingqAnkiSyncTemplate")
    template['qfmt'] = "{{Front}}"
    template['afmt'] = "{{FrontSide}}<hr id=answer>{{Back}}"
    mw.col.models.addTemplate(model, template)
    mw.col.models.add(model)
    mw.col.models.setCurrent(model)
    mw.col.models.save(model)
    return model

def CreateNoteTypeIfNotExist(noteTypeName: str, noteFields: List, deckName: str):
    if not mw.col.models.byName(noteTypeName):
        CreateNoteType(noteTypeName, noteFields)

def GetAllCardsInDeck(deckName: str) -> List[AnkiCard]:
    cards = []
    cardIds = mw.col.find_cards(f'deck:"{deckName}"')
    for cardId in cardIds:
        card = mw.col.get_card(cardId)
        card = _CreateAnkiCardObject(card, cardId)
        cards.append(card)
    return cards

def GetAllDeckNames() -> List[str]:
    return mw.col.decks.all_names()

def GetIntervalFromCard(cardId) -> int:
    interval = mw.col.db.scalar("select ivl from cards where id = ?", cardId)
    return 0 if interval is None else interval

def _CreateAnkiCardObject(card, cardId) -> AnkiCard:
    return AnkiCard(
        card.note()["LingqPK"],
        card.note()["Front"],
        card.note()["Back"],
        GetIntervalFromCard(cardId)
    )

Converter.py

import sys, os
sys.path.append(os.path.realpath(f"./{os.path.dirname(__file__)}"))

from typing import List, Dict
from Models.Lingq import Lingq
from Models.AnkiCard import AnkiCard

def ConvertAnkiCardsToLingqs(ankiCards: List[AnkiCard], statusToInterval: Dict[int,int]) -> List[Lingq]:
    lingqs = []
    for card in ankiCards:
        lingqs.append(
            Lingq(
                card.primaryKey,
                card.word,
                card.translation,
                _ConvertAnkiIntervalToLingqStatus(card.interval, statusToInterval),
                None
        ))
    return lingqs

def ConvertLingqsToAnkiCards(lingqs: List[Lingq], statusToInterval: Dict[int,int]) -> List[AnkiCard]:
    ankiCards = []
    for lingq in lingqs:
        ankiCards.append(
            AnkiCard(
                lingq.primaryKey,
                lingq.word,
                lingq.translation,
                _ConvertLingqStatusToAnkiInterval(lingq.status, statusToInterval)
        ))
    return ankiCards

def _ConvertLingqStatusToAnkiInterval(status: int, statusToInterval: Dict[int,int]) -> str:
    for lingqStatus, ankiInterval in statusToInterval.items():
        if status <= lingqStatus:
            return ankiInterval
    return max(statusToInterval.values())

def _ConvertAnkiIntervalToLingqStatus(interval: int, statusToInterval: Dict[int,int]) -> int:
    for lingqStatus, ankiInterval in statusToInterval.items():
        if interval <= ankiInterval:
            return lingqStatus
    return max(statusToInterval.keys())
thags commented 1 year ago

Thanks for sending this in. I'll add this in to the code as long as it doesn't cause any issues on the windows side. I don't have a mac to test with.