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.

283 lines
11 KiB

8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
5 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
5 months ago
8 months ago
8 months ago
8 months ago
5 months ago
8 months ago
8 months ago
8 months ago
5 months ago
8 months ago
8 months ago
5 months ago
  1. #!/usr/bin/python3
  2. import pyamf
  3. import shutil
  4. import json
  5. import os
  6. import glob
  7. import argparse
  8. import unicodedata
  9. import random
  10. import re
  11. import sys
  12. import readline
  13. import datetime
  14. DATA_DIR = os.environ.get('XDG_DATA_HOME', os.environ['HOME'] + '/.local/share') + '/klette'
  15. DEPRECATED_VOKABELN_FILE = DATA_DIR + '/vokabeln.json'
  16. VOKABELN_DIR = DATA_DIR + '/vokabeln/'
  17. STATUS_FILE = DATA_DIR + '/status.json'
  18. AUDIO_BASE = DATA_DIR + '/audio/'
  19. class bcolors:
  20. HEADER = '\033[95m'
  21. OKBLUE = '\033[94m'
  22. OKCYAN = '\033[96m'
  23. OKGREEN = '\033[92m'
  24. WARNING = '\033[93m'
  25. FAIL = '\033[91m'
  26. ENDC = '\033[0m'
  27. BOLD = '\033[1m'
  28. UNDERLINE = '\033[4m'
  29. LINK_START = '\033]8;;'
  30. LINK_MIDDLE = '\033\\'
  31. LINK_END = '\033]8;;\033\\'
  32. def import_vokabeln(file_name, audio_base):
  33. os.makedirs(AUDIO_BASE, exist_ok=True)
  34. palabras = []
  35. with open(file_name, 'rb') as reader:
  36. obj = pyamf.decode(reader.read())
  37. for unidad_data in obj.readElement()['lektionList']:
  38. unidad = unidad_data['name']
  39. for paso_data in unidad_data['lektionsTeilList']:
  40. paso = paso_data['name']
  41. for palabra in paso_data['entryList']:
  42. audio = palabra['vorderSeite']['swTonAbgleichwort']
  43. palabras.append({
  44. 'id': palabra['matId'],
  45. 'de': palabra['rueckSeite']['text'],
  46. 'es': palabra['vorderSeite']['text'],
  47. 'audio': audio,
  48. 'unidad': unidad,
  49. 'paso': paso
  50. })
  51. if audio:
  52. shutil.copy2(audio_base + audio + '.bin',
  53. AUDIO_BASE + audio + '.aac')
  54. with open(VOKABELN_DIR + '/import-' + datetime.datetime.now().isoformat() + '.json', 'x') as writer:
  55. writer.write(json.dumps(palabras))
  56. def import_status(file_name):
  57. os.makedirs(DATA_DIR, exist_ok=True)
  58. statuses = {}
  59. with open(file_name, 'rb') as reader:
  60. obj = pyamf.decode(reader.read()).readElement()[0]
  61. for idx in obj:
  62. item = obj[idx]
  63. statuses[item['matId']] = item['statusWortschatz']
  64. with open(STATUS_FILE, 'x') as writer:
  65. writer.write(json.dumps(statuses))
  66. def vergleiche(respuesta, expecta):
  67. if respuesta == "": return 4
  68. if respuesta[0] == '!' and expecta[0] == '¡':
  69. respuesta = '¡' + respuesta[1:]
  70. if respuesta[0] == '?' and expecta[0] == '¿':
  71. respuesta = '¿' + respuesta[1:]
  72. respuesta = re.sub('(~n|n~)', 'ñ', respuesta)
  73. expecta = re.sub(' \((irr.|pl.)\)', '', expecta)
  74. respuesta = unicodedata.normalize('NFD', respuesta)
  75. expecta = unicodedata.normalize('NFD', expecta)
  76. respuesta = re.sub(' ?([!?¿¡….]) ?', r'\1', respuesta)
  77. expecta = re.sub(' ?([!?¿¡….]) ?', r'\1', expecta)
  78. respuesta = re.sub(r'\.\.\.', '', respuesta)
  79. expecta = re.sub(r'\.\.\.', '', expecta)
  80. if respuesta == expecta: return 0
  81. respuesta = re.sub('[!?¿¡…. ]', '', respuesta)
  82. expecta = re.sub('[!?¿¡…. ]', '', expecta)
  83. if respuesta == expecta: return 1
  84. respuesta = respuesta.encode('ASCII', 'ignore').decode('ASCII')
  85. expecta = expecta.encode('ASCII', 'ignore').decode('ASCII')
  86. if respuesta == expecta: return 2
  87. respuesta = respuesta.casefold()
  88. expecta = expecta.casefold()
  89. if respuesta == expecta: return 3
  90. return 4
  91. assert vergleiche("e", "e") == 0
  92. assert vergleiche("é", "e") == 2
  93. assert vergleiche("Bien", "bien") == 3
  94. assert vergleiche("quétal", "Qué tal") == 3
  95. assert vergleiche("Buenas noches", "¡Buenas noches!") == 1
  96. assert vergleiche("ser", "ser (irr.)") == 0
  97. assert vergleiche("hacer algo", "hacer (irr.) algo") == 0
  98. assert vergleiche("padres", "padres (pl.)") == 0
  99. assert vergleiche("¿De dónde …?", "¿De dónde… ?") == 0
  100. assert vergleiche("el nombre", "el apellido") == 4
  101. assert vergleiche("Tengo … a~nos.", "Tengo... años.") == 0
  102. assert vergleiche("¿Cuántos a~nos tienes?", "¿Cuántos años tienes?") == 0
  103. class Resultado:
  104. CORRECTO = 0
  105. BIEN = 1
  106. NO_BIEN = 2
  107. MAL = 3
  108. ADIOS = 4
  109. def einzelne_abfrage(palabra, status):
  110. if status == None:
  111. status_text = f"{bcolors.OKCYAN}neu{bcolors.ENDC}"
  112. status = 0
  113. else:
  114. status_text = f"({status})"
  115. try:
  116. respuesta = input(f"{status_text} {bcolors.BOLD}{palabra['de']}{bcolors.ENDC} {bcolors.OKCYAN}")
  117. except EOFError:
  118. print()
  119. return Resultado.ADIOS
  120. finally:
  121. print(bcolors.ENDC, end="")
  122. print('\033[{}C\033[1A'.format(4 + len(palabra['de']) + 1 + len(respuesta)), end="")
  123. resultado = vergleiche(respuesta, palabra['es'])
  124. if resultado == 0:
  125. bien = Resultado.CORRECTO
  126. else:
  127. match status:
  128. case 0: bien = Resultado.BIEN if resultado < 4 else Resultado.NO_BIEN
  129. case 1: bien = Resultado.BIEN if resultado < 3 else (Resultado.NO_BIEN if resultado < 4 else Resultado.MAL)
  130. case 2: bien = Resultado.BIEN if resultado < 2 else (Resultado.NO_BIEN if resultado < 3 else Resultado.MAL)
  131. case 3: bien = Resultado.BIEN if resultado < 2 else Resultado.MAL
  132. case 4: bien = Resultado.BIEN if resultado < 1 else Resultado.MAL
  133. if 'audio' in palabra:
  134. print(f" {bcolors.LINK_START}file://{AUDIO_BASE}{palabra['audio']}.aac{bcolors.LINK_MIDDLE}",end="")
  135. else:
  136. print(" ",end="")
  137. if bien == Resultado.CORRECTO:
  138. print(f"{bcolors.OKGREEN}✓{bcolors.ENDC}",end="")
  139. elif bien == Resultado.BIEN:
  140. print(f"{bcolors.WARNING}{palabra['es']}{bcolors.ENDC}",end="")
  141. else:
  142. print(f"{bcolors.FAIL}{palabra['es']}{bcolors.ENDC}",end="")
  143. if 'audio' in palabra:
  144. print(bcolors.LINK_END,end="")
  145. try:
  146. if input(" ") == "+":
  147. bien = Resultado.CORRECTO
  148. except EOFError:
  149. print()
  150. return Resultado.ADIOS
  151. return bien
  152. class Sesion:
  153. def __init__(self, palabras, status):
  154. self.palabras = palabras
  155. self.status = status
  156. self.bien = 0
  157. self.mal = 0
  158. def otra_vez(self, quiero_unidad):
  159. unidad = False
  160. unidad_no = -1
  161. paso = False
  162. cur_palabras = False
  163. for palabra in self.palabras:
  164. if unidad != palabra['unidad']:
  165. unidad_no += 1
  166. unidad = palabra['unidad']
  167. if quiero_unidad == None or quiero_unidad == unidad_no:
  168. if self.status.get(palabra['id'], None) == 5:
  169. self.status[palabra['id']] = 4
  170. def empezar(self, quiero_unidad):
  171. unidad = False
  172. unidad_no = -1
  173. paso = False
  174. cur_palabras = False
  175. for palabra in self.palabras:
  176. if paso != palabra['paso']:
  177. if cur_palabras != False and (quiero_unidad == None or quiero_unidad == unidad_no):
  178. if self.hace_paso(unidad, paso, cur_palabras) == Resultado.ADIOS:
  179. return
  180. cur_palabras = {0: [], 1: [], 2: [], 3: [], 4: [], 5: [], None: []}
  181. if unidad != palabra['unidad']:
  182. unidad_no += 1
  183. unidad = palabra['unidad']
  184. paso = palabra['paso']
  185. cur_palabras[self.status.get(palabra['id'])].append(palabra)
  186. if cur_palabras != False and (quiero_unidad == None or quiero_unidad == unidad_no):
  187. if self.hace_paso(unidad, paso, cur_palabras) == Resultado.ADIOS:
  188. return
  189. def hace_paso(self, unidad, paso, cur_palabras):
  190. c = [len(cur_palabras[x]) for x in cur_palabras]
  191. 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})")
  192. for n in range(5): # 1..4, no 5
  193. if self.hace_palabras(cur_palabras[n], n) == Resultado.ADIOS:
  194. return Resultado.ADIOS
  195. n = None
  196. if self.hace_palabras(cur_palabras[n], n) == Resultado.ADIOS:
  197. return Resultado.ADIOS
  198. def hace_palabras(self, palabras, status):
  199. random.shuffle(palabras)
  200. for palabra in palabras:
  201. match einzelne_abfrage(palabra, status):
  202. case Resultado.CORRECTO:
  203. self.status[palabra['id']] = (status or 0) + (2 if (status or 0) < 2 else 1)
  204. self.bien += 1
  205. case Resultado.BIEN:
  206. self.status[palabra['id']] = (status or 0) + 1
  207. self.bien += 1
  208. case Resultado.NO_BIEN:
  209. continue
  210. case Resultado.MAL:
  211. self.status[palabra['id']] = status - 1
  212. self.mal += 1
  213. case Resultado.ADIOS:
  214. return Resultado.ADIOS
  215. with open(STATUS_FILE + '.new', 'w') as f:
  216. json.dump(self.status, f)
  217. f.flush()
  218. os.fsync(f.fileno())
  219. with open(STATUS_FILE + '.new', 'r') as f:
  220. pass
  221. os.replace(STATUS_FILE + '.new', STATUS_FILE)
  222. def abfrage(parser, quiero_unidad, otra_vez):
  223. random.seed()
  224. try:
  225. palabras = []
  226. os.makedirs(VOKABELN_DIR, exist_ok=True)
  227. try:
  228. os.rename(DEPRECATED_VOKABELN_FILE, os.path.join(VOKABELN_DIR, 'vokabeln.json'))
  229. except FileNotFoundError:
  230. pass
  231. for filename in glob.glob(os.path.join(VOKABELN_DIR, '*.json')):
  232. with open(filename, 'r') as f:
  233. palabras += json.load(f)
  234. with open(STATUS_FILE, 'r') as f:
  235. status = json.load(f)
  236. except FileNotFoundError:
  237. print(f"{bcolors.FAIL}Daten können nicht geladen werden, hast du sie schon importiert?{bcolors.ENDC}")
  238. print()
  239. parser.print_help()
  240. return
  241. sesion = Sesion(palabras, status)
  242. if otra_vez:
  243. sesion.otra_vez(quiero_unidad)
  244. sesion.empezar(quiero_unidad)
  245. print(f'{bcolors.OKGREEN}+{sesion.bien}{bcolors.ENDC} / {bcolors.FAIL}-{sesion.mal}{bcolors.ENDC}')
  246. parser = argparse.ArgumentParser()
  247. parser.add_argument('--import-data', type=str, help="Path to assets", metavar="DIR")
  248. default_status_file = os.environ['HOME'] + '/klett/1266/vokabeltrainerData147'
  249. parser.add_argument('--import-status', type=str, help="Path to AMF File, defaults to " + default_status_file, metavar="FILE", nargs='?', const=default_status_file)
  250. parser.add_argument('--unidad', type=int)
  251. parser.add_argument('--otra-vez', action='store_true')
  252. args = parser.parse_args()
  253. if args.import_data:
  254. import_vokabeln(glob.glob(os.path.join(args.import_data, 'amf/vokabelTrainer*.amf'))[0],
  255. args.import_data + '/amf/medium/')
  256. elif args.import_status:
  257. import_status(args.import_status)
  258. else:
  259. abfrage(parser, args.unidad, args.otra_vez)