#!/usr/bin/python3 import pyamf import shutil import json import os import glob import argparse import unicodedata import random import re import sys import readline import datetime DATA_DIR = os.environ.get('XDG_DATA_HOME', os.environ['HOME'] + '/.local/share') + '/klette' DEPRECATED_VOKABELN_FILE = DATA_DIR + '/vokabeln.json' VOKABELN_DIR = DATA_DIR + '/vokabeln/' STATUS_FILE = DATA_DIR + '/status.json' AUDIO_BASE = DATA_DIR + '/audio/' class bcolors: HEADER = '\033[95m' OKBLUE = '\033[94m' OKCYAN = '\033[96m' OKGREEN = '\033[92m' WARNING = '\033[93m' FAIL = '\033[91m' ENDC = '\033[0m' BOLD = '\033[1m' UNDERLINE = '\033[4m' LINK_START = '\033]8;;' LINK_MIDDLE = '\033\\' LINK_END = '\033]8;;\033\\' def import_vokabeln(file_name, audio_base): os.makedirs(AUDIO_BASE, exist_ok=True) palabras = [] with open(file_name, 'rb') as reader: obj = pyamf.decode(reader.read()) for unidad_data in obj.readElement()['lektionList']: unidad = unidad_data['name'] for paso_data in unidad_data['lektionsTeilList']: paso = paso_data['name'] for palabra in paso_data['entryList']: audio = palabra['vorderSeite']['swTonAbgleichwort'] palabras.append({ 'id': palabra['matId'], 'de': palabra['rueckSeite']['text'], 'es': palabra['vorderSeite']['text'], 'audio': audio, 'unidad': unidad, 'paso': paso }) if audio: shutil.copy2(audio_base + audio + '.bin', AUDIO_BASE + audio + '.aac') with open(VOKABELN_DIR + '/import-' + datetime.datetime.now().isoformat() + '.json', 'x') as writer: writer.write(json.dumps(palabras)) def import_status(file_name): os.makedirs(DATA_DIR, exist_ok=True) statuses = {} with open(file_name, 'rb') as reader: obj = pyamf.decode(reader.read()).readElement()[0] for idx in obj: item = obj[idx] statuses[item['matId']] = item['statusWortschatz'] with open(STATUS_FILE, 'x') as writer: writer.write(json.dumps(statuses)) def vergleiche(respuesta, expecta): if respuesta == "": return 4 if respuesta[0] == '!' and expecta[0] == '¡': respuesta = '¡' + respuesta[1:] if respuesta[0] == '?' and expecta[0] == '¿': respuesta = '¿' + respuesta[1:] respuesta = re.sub('(~n|n~)', 'ñ', respuesta) expecta = re.sub(' \((irr.|pl.)\)', '', expecta) respuesta = unicodedata.normalize('NFD', respuesta) expecta = unicodedata.normalize('NFD', expecta) respuesta = re.sub(' ?([!?¿¡….]) ?', r'\1', respuesta) expecta = re.sub(' ?([!?¿¡….]) ?', r'\1', expecta) respuesta = re.sub(r'\.\.\.', '…', respuesta) expecta = re.sub(r'\.\.\.', '…', expecta) if respuesta == expecta: return 0 respuesta = re.sub('[!?¿¡…. ]', '', respuesta) expecta = re.sub('[!?¿¡…. ]', '', expecta) if respuesta == expecta: return 1 respuesta = respuesta.encode('ASCII', 'ignore').decode('ASCII') expecta = expecta.encode('ASCII', 'ignore').decode('ASCII') if respuesta == expecta: return 2 respuesta = respuesta.casefold() expecta = expecta.casefold() if respuesta == expecta: return 3 return 4 assert vergleiche("e", "e") == 0 assert vergleiche("é", "e") == 2 assert vergleiche("Bien", "bien") == 3 assert vergleiche("quétal", "Qué tal") == 3 assert vergleiche("Buenas noches", "¡Buenas noches!") == 1 assert vergleiche("ser", "ser (irr.)") == 0 assert vergleiche("hacer algo", "hacer (irr.) algo") == 0 assert vergleiche("padres", "padres (pl.)") == 0 assert vergleiche("¿De dónde …?", "¿De dónde… ?") == 0 assert vergleiche("el nombre", "el apellido") == 4 assert vergleiche("Tengo … a~nos.", "Tengo... años.") == 0 assert vergleiche("¿Cuántos a~nos tienes?", "¿Cuántos años tienes?") == 0 class Resultado: CORRECTO = 0 BIEN = 1 NO_BIEN = 2 MAL = 3 ADIOS = 4 def einzelne_abfrage(palabra, status): if status == None: status_text = f"{bcolors.OKCYAN}neu{bcolors.ENDC}" status = 0 else: status_text = f"({status})" try: respuesta = input(f"{status_text} {bcolors.BOLD}{palabra['de']}{bcolors.ENDC} {bcolors.OKCYAN}") except EOFError: print() return Resultado.ADIOS finally: print(bcolors.ENDC, end="") print('\033[{}C\033[1A'.format(4 + len(palabra['de']) + 1 + len(respuesta)), end="") resultado = vergleiche(respuesta, palabra['es']) if resultado == 0: bien = Resultado.CORRECTO else: match status: case 0: bien = Resultado.BIEN if resultado < 4 else Resultado.NO_BIEN case 1: bien = Resultado.BIEN if resultado < 3 else (Resultado.NO_BIEN if resultado < 4 else Resultado.MAL) case 2: bien = Resultado.BIEN if resultado < 2 else (Resultado.NO_BIEN if resultado < 3 else Resultado.MAL) case 3: bien = Resultado.BIEN if resultado < 2 else Resultado.MAL case 4: bien = Resultado.BIEN if resultado < 1 else Resultado.MAL if 'audio' in palabra: print(f" {bcolors.LINK_START}file://{AUDIO_BASE}{palabra['audio']}.aac{bcolors.LINK_MIDDLE}",end="") else: print(" ",end="") if bien == Resultado.CORRECTO: print(f"{bcolors.OKGREEN}✓{bcolors.ENDC}",end="") elif bien == Resultado.BIEN: print(f"{bcolors.WARNING}{palabra['es']}{bcolors.ENDC}",end="") else: print(f"{bcolors.FAIL}{palabra['es']}{bcolors.ENDC}",end="") if 'audio' in palabra: print(bcolors.LINK_END,end="") try: if input(" ") == "+": bien = Resultado.CORRECTO except EOFError: print() return Resultado.ADIOS return bien class Sesion: def __init__(self, palabras, status): self.palabras = palabras self.status = status self.bien = 0 self.mal = 0 def otra_vez(self, quiero_unidad): unidad = False unidad_no = -1 paso = False cur_palabras = False for palabra in self.palabras: if unidad != palabra['unidad']: unidad_no += 1 unidad = palabra['unidad'] if quiero_unidad == None or quiero_unidad == unidad_no: if self.status.get(palabra['id'], None) == 5: self.status[palabra['id']] = 4 def empezar(self, quiero_unidad): unidad = False unidad_no = -1 paso = False cur_palabras = False for palabra in self.palabras: if paso != palabra['paso']: if cur_palabras != False and (quiero_unidad == None or quiero_unidad == unidad_no): if self.hace_paso(unidad, paso, cur_palabras) == Resultado.ADIOS: return cur_palabras = {0: [], 1: [], 2: [], 3: [], 4: [], 5: [], None: []} if unidad != palabra['unidad']: unidad_no += 1 unidad = palabra['unidad'] paso = palabra['paso'] cur_palabras[self.status.get(palabra['id'])].append(palabra) if cur_palabras != False and (quiero_unidad == None or quiero_unidad == unidad_no): if self.hace_paso(unidad, paso, cur_palabras) == Resultado.ADIOS: return def hace_paso(self, unidad, paso, cur_palabras): c = [len(cur_palabras[x]) for x in cur_palabras] print(f"{bcolors.BOLD}{unidad}{bcolors.ENDC}: {paso} ({c[0] + c[6]}/{c[1]}/{c[2]}/{c[3]}/{c[4]}/{bcolors.OKGREEN}{c[5]}{bcolors.ENDC})") n = None if self.hace_palabras(cur_palabras[n], n) == Resultado.ADIOS: return Resultado.ADIOS for n in range(5): # 1..4, no 5 if self.hace_palabras(cur_palabras[n], n) == Resultado.ADIOS: return Resultado.ADIOS def hace_palabras(self, palabras, status): random.shuffle(palabras) for palabra in palabras: match einzelne_abfrage(palabra, status): case Resultado.CORRECTO: self.status[palabra['id']] = (status or 0) + (2 if (status or 0) < 2 else 1) self.bien += 1 case Resultado.BIEN: self.status[palabra['id']] = (status or 0) + 1 self.bien += 1 case Resultado.NO_BIEN: continue case Resultado.MAL: self.status[palabra['id']] = status - 1 self.mal += 1 case Resultado.ADIOS: return Resultado.ADIOS with open(STATUS_FILE + '.new', 'w') as f: json.dump(self.status, f) f.flush() os.fsync(f.fileno()) with open(STATUS_FILE + '.new', 'r') as f: pass os.replace(STATUS_FILE + '.new', STATUS_FILE) def abfrage(parser, quiero_unidad, otra_vez): random.seed() try: palabras = [] os.makedirs(VOKABELN_DIR, exist_ok=True) try: os.rename(DEPRECATED_VOKABELN_FILE, os.path.join(VOKABELN_DIR, 'vokabeln.json')) except FileNotFoundError: pass for filename in glob.glob(os.path.join(VOKABELN_DIR, '*.json')): with open(filename, 'r') as f: palabras += json.load(f) with open(STATUS_FILE, 'r') as f: status = json.load(f) except FileNotFoundError: print(f"{bcolors.FAIL}Daten können nicht geladen werden, hast du sie schon importiert?{bcolors.ENDC}") print() parser.print_help() return sesion = Sesion(palabras, status) if otra_vez: sesion.otra_vez(quiero_unidad) sesion.empezar(quiero_unidad) print(f'{bcolors.OKGREEN}+{sesion.bien}{bcolors.ENDC} / {bcolors.FAIL}-{sesion.mal}{bcolors.ENDC}') parser = argparse.ArgumentParser() parser.add_argument('--import-data', type=str, help="Path to assets", metavar="DIR") default_status_file = os.environ['HOME'] + '/klett/1266/vokabeltrainerData147' parser.add_argument('--import-status', type=str, help="Path to AMF File, defaults to " + default_status_file, metavar="FILE", nargs='?', const=default_status_file) parser.add_argument('--unidad', type=int) parser.add_argument('--otra-vez', action='store_true') args = parser.parse_args() if args.import_data: import_vokabeln(glob.glob(os.path.join(args.import_data, 'amf/vokabelTrainer*.amf'))[0], args.import_data + '/amf/medium/') elif args.import_status: import_status(args.import_status) else: abfrage(parser, args.unidad, args.otra_vez)