$error, 'result' => $result] = $this->client->request( 'POST', 'http://localhost:8765', ['json' => [ 'action' => $action, 'version' => 6, 'params' => $params, ]] )->toArray(); if ($error != null) { throw new \Exception('AnkiConnect returned error: ' . $error); } return $result; } /** The note's id is updated on success. * @return bool True on success */ public function addNote(Note &$note, string $deckName): bool { $note->setId($this->request('addNote', ['note' => [ 'deckName' => $deckName, 'modelName' => $note->getModel(), 'fields' => $note->getFields(), 'options' => ['allowDuplicate' => false], 'tags' => array_merge($note->getTags(), ['anker_made']), ]])); return $note->getId() !== null; } public function findNotesIds(string $query): array { return $this->request('findNotes', ['query' => $query]); } public function getAllSentenceListeningNoteIds(): array { $query = sprintf( '"note:%s"', SentenceListeningNote::MODEL_NAME, ); return $this->request('findNotes', ['query' => $query]); } public function getAllSentenceNoteIds(): array { return $this->request( 'findNotes', ['query' => '"note:Japanese sentences" -is:suspended'], ); } /** Give an array of IDs, the note Infos are returned. if info for a given * doesn't exist, it is assigned to null instead of the default []. */ public function getNotesInfo(array $noteIds): array { $noteInfos = $this->request('notesInfo', ['notes' => $noteIds]); foreach ($noteInfos as &$noteInfo) { if ([] === $noteInfo) $noteInfo = null; } return $noteInfos; } /** Returns info form note given an ID, returns null if it doesn't exist */ public function getNoteInfo(int $noteId): ?array { return $this->getNotesInfo([$noteId])[0]; } public function getNotes(array $nids): array { return array_map(self::parseNoteInfo(...), $this->getNotesInfo($nids)); } public function getNote(int $nid): ?Note { $noteInfo = $this->getNoteInfo($nid) ?? throw new \Exception("Note $nid not found."); return self::parseNoteInfo($noteInfo); } private static function parseNoteInfo(array $noteInfo): Note { return match ($noteInfo['modelName']) { SentenceNote::MODEL_NAME => SentenceNote::fromAnki($noteInfo), SentenceListeningNote::MODEL_NAME => SentenceListeningNote::fromAnki($noteInfo), default => throw new \Exception(sprintf( 'Unrecognized Note "%s" of type "%s"', $noteInfo['noteId'], $noteInfo['modelName'], )) }; } public function getLatestNote(): ?Note { // NoteIDs are just timestamps in milliseconds, so the latest is just // the biggest numerically $latestId = max($this->getAllSentenceNoteIds()); return $this->getNote($latestId); } public function updateNote(Note $note) { $this->request('guiBrowse', ['query' => 'nid:1']); $this->request('updateNoteFields', ['note' => $note->toAnki()]); $this->request('guiBrowse', ['query' => 'nid:' . $note->getId()]); } /** @return array */ public function getKnownSlnKanjiCounts(): array { $allListeningIds = $this->getAllSentenceListeningNoteIds(); $ret = []; foreach ($this->getNotes($allListeningIds) as $slNote) { assert($slNote instanceof SentenceListeningNote); $termKanji = Japanese::getOnlyKanji($slNote->getTerm()->getKanji()); $len = mb_strlen($termKanji); for ($i = 0; $i < $len; $i++) { $kanji = mb_substr($termKanji, $i, 1); $ret[$kanji] ??= 0; $ret[$kanji]++; } } return $ret; } }