You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

199 lines
7.3 KiB

8 months ago
  1. #!/usr/bin/python3
  2. import pyamf
  3. import shutil
  4. import json
  5. import os
  6. import argparse
  7. import unicodedata
  8. import random
  9. import re
  10. import sys
  11. import readline
  12. DATA_DIR = os.environ['XDG_DATA_HOME'] + '/klette'
  13. VOKABELN_FILE = DATA_DIR + '/vokabeln.json'
  14. STATUS_FILE = DATA_DIR + '/status.json'
  15. AUDIO_BASE = DATA_DIR + '/audio/'
  16. class bcolors:
  17. HEADER = '\033[95m'
  18. OKBLUE = '\033[94m'
  19. OKCYAN = '\033[96m'
  20. OKGREEN = '\033[92m'
  21. WARNING = '\033[93m'
  22. FAIL = '\033[91m'
  23. ENDC = '\033[0m'
  24. BOLD = '\033[1m'
  25. UNDERLINE = '\033[4m'
  26. LINK_START = '\033]8;;'
  27. LINK_MIDDLE = '\033\\'
  28. LINK_END = '\033]8;;\033\\'
  29. def import_vokabeln(file_name, audio_base):
  30. os.makedirs(AUDIO_BASE, exist_ok=True)
  31. palabras = []
  32. with open(file_name, 'rb') as reader:
  33. obj = pyamf.decode(reader.read())
  34. for unidad_data in obj.readElement()['lektionList']:
  35. unidad = unidad_data['name']
  36. for paso_data in unidad_data['lektionsTeilList']:
  37. paso = paso_data['name']
  38. for palabra in paso_data['entryList']:
  39. audio = palabra['vorderSeite']['swTonAbgleichwort']
  40. palabras.append({
  41. 'id': palabra['matId'],
  42. 'de': palabra['rueckSeite']['text'],
  43. 'es': palabra['vorderSeite']['text'],
  44. 'audio': audio,
  45. 'unidad': unidad,
  46. 'paso': paso
  47. })
  48. shutil.copy2(audio_base + audio + '.bin',
  49. AUDIO_BASE + audio + '.aac')
  50. with open(VOKABELN_FILE, 'x') as writer:
  51. writer.write(json.dumps(palabras))
  52. def import_status(file_name):
  53. os.makedirs(DATA_DIR, exist_ok=True)
  54. statuses = {}
  55. with open(file_name, 'rb') as reader:
  56. obj = pyamf.decode(reader.read()).readElement()[0]
  57. for idx in obj:
  58. item = obj[idx]
  59. statuses[item['matId']] = item['statusWortschatz']
  60. with open(STATUS_FILE, 'x') as writer:
  61. writer.write(json.dumps(statuses))
  62. def vergleiche(respuesta, expecta):
  63. if respuesta == "": return 4
  64. if respuesta[0] == '!' and expecta[0] == '¡':
  65. respuesta = '¡' + respuesta[1:]
  66. if respuesta[0] == '?' and expecta[0] == '¿':
  67. respuesta = '¿' + respuesta[1:]
  68. respuesta = unicodedata.normalize('NFD', respuesta)
  69. expecta = unicodedata.normalize('NFD', expecta)
  70. if respuesta == expecta: return 0
  71. respuesta = respuesta.encode('ASCII', 'ignore').decode('ASCII')
  72. expecta = expecta.encode('ASCII', 'ignore').decode('ASCII')
  73. if respuesta == expecta: return 1
  74. respuesta = respuesta.casefold()
  75. expecta = expecta.casefold()
  76. if respuesta == expecta: return 2
  77. respuesta = re.sub('\W', '', respuesta)
  78. expecta = re.sub('\W', '', expecta)
  79. if respuesta == expecta: return 3
  80. return 4
  81. assert vergleiche("e", "e") == 0
  82. assert vergleiche("é", "e") == 1
  83. assert vergleiche("Bien", "bien") == 2
  84. assert vergleiche("quétal", "Qué tal") == 3
  85. assert vergleiche("Buenas Noches", "¡Buenas noches!") == 3
  86. class Resultado:
  87. BIEN = 1
  88. MAL = 2
  89. ADIOS = 3
  90. def einzelne_abfrage(palabra, status):
  91. status_text = f"({status})" if status > 0 else f"{bcolors.OKGREEN}neu{bcolors.ENDC}"
  92. try:
  93. respuesta = input(f"{status_text} {bcolors.BOLD}{palabra['de']}{bcolors.ENDC} {bcolors.OKCYAN}")
  94. except EOFError:
  95. print()
  96. return Resultado.ADIOS
  97. finally:
  98. print(bcolors.ENDC, end="")
  99. print('\033[{}C\033[1A'.format(4 + len(palabra['de']) + 1 + len(respuesta)), end="")
  100. resultado = vergleiche(respuesta, palabra['es'])
  101. match status:
  102. case 0: bien = resultado < 4
  103. case 1: bien = resultado < 3
  104. case 2: bien = resultado < 3
  105. case 3: bien = resultado < 2
  106. case 4: bien = resultado < 1
  107. print(f" {bcolors.LINK_START}file://{AUDIO_BASE}{palabra['audio']}.aac{bcolors.LINK_MIDDLE}",end="")
  108. if bien and resultado == 0:
  109. print(f"{bcolors.OKGREEN}✓{bcolors.ENDC}",end="")
  110. elif bien:
  111. print(f"{bcolors.WARNING}{palabra['es']}{bcolors.ENDC}",end="")
  112. else:
  113. print(f"{bcolors.FAIL}{palabra['es']}{bcolors.ENDC}",end="")
  114. print(bcolors.LINK_END,end="")
  115. try:
  116. input(" ")
  117. except EOFError:
  118. print()
  119. return Resultado.ADIOS
  120. return Resultado.BIEN if bien else Resultado.MAL
  121. def abfrage(parser):
  122. random.seed()
  123. try:
  124. with open(VOKABELN_FILE, 'r') as f:
  125. palabras = json.load(f)
  126. with open(STATUS_FILE, 'r') as f:
  127. status = json.load(f)
  128. except FileNotFoundError:
  129. print(f"{bcolors.FAIL}Daten können nicht geladen werden, hast du sie schon importiert?{bcolors.ENDC}")
  130. print()
  131. parser.print_help()
  132. return
  133. unidad = False
  134. paso = False
  135. cur_palabras = False
  136. bien = 0
  137. mal = 0
  138. for palabra in palabras:
  139. if paso != palabra['paso']:
  140. if cur_palabras != False:
  141. c = [len(cur_palabras[x]) for x in cur_palabras]
  142. print(f"{bcolors.BOLD}{unidad}{bcolors.ENDC}: {paso} (",end="")
  143. print(*c, sep="/", end=")")
  144. print()
  145. for n in range(5): # 1..4, no 5
  146. random.shuffle(cur_palabras[n])
  147. for palabra in cur_palabras[n]:
  148. s = status.get(palabra['id'], 0)
  149. match einzelne_abfrage(palabra, s):
  150. case Resultado.BIEN:
  151. status[palabra['id']] = s + 1
  152. bien+=1
  153. case Resultado.MAL:
  154. if s > 0:
  155. status[palabra['id']] = s - 1
  156. mal+=1
  157. continue
  158. case Resultado.ADIOS:
  159. print(f'{bcolors.OKGREEN}+{bien}{bcolors.ENDC} / {bcolors.FAIL}-{mal}{bcolors.ENDC}')
  160. return
  161. with open(STATUS_FILE + '.new', 'w') as f:
  162. json.dump(status, f)
  163. f.flush()
  164. os.fsync(f.fileno())
  165. with open(STATUS_FILE + '.new', 'r') as f:
  166. status = json.load(f)
  167. os.replace(STATUS_FILE + '.new', STATUS_FILE)
  168. cur_palabras = {0: [], 1: [], 2: [], 3: [], 4: [], 5: []}
  169. unidad = palabra['unidad']
  170. paso = palabra['paso']
  171. cur_palabras[status.get(palabra['id'], 0)].append(palabra)
  172. parser = argparse.ArgumentParser()
  173. default_data_file = os.environ['PWD'] + '/assets/amf/vokabelTrainer147.amf'
  174. parser.add_argument('--import-data', type=str, help="Path to assets", metavar="DIR")
  175. default_status_file = os.environ['HOME'] + '/klett/1266/vokabeltrainerData147'
  176. parser.add_argument('--import-status', type=str, help="Path to AMF File, defaults to " + default_status_file, metavar="FILE", nargs='?', const=default_status_file)
  177. args = parser.parse_args()
  178. if args.import_data:
  179. import_vokabeln(args.import_data + '/amf/vokabelTrainer147.amf',
  180. args.import_data + '/amf/medium/')
  181. elif args.import_status:
  182. import_status(args.import_status)
  183. else:
  184. abfrage(parser)