Skip to content

Commit

Permalink
Merge pull request #3 from dlangevi/main
Browse files Browse the repository at this point in the history
Implement more anki-connect api calls
  • Loading branch information
atselvan authored Mar 14, 2023
2 parents 3310e39 + f3e9a5b commit 5fb4e18
Show file tree
Hide file tree
Showing 31 changed files with 1,204 additions and 163 deletions.
40 changes: 39 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,44 @@ if restErr != nil {
}
```

### Get Notes

```go
client := ankiconnect.NewClient()

// Get the Note Ids of cards due today
nodeIds, restErr := client.Notes.Get("prop:due=0")
if restErr != nil {
log.Fatal(restErr)
}

// Get the Note data of cards due today
notes, restErr := client.Notes.Get("prop:due=0")
if restErr != nil {
log.Fatal(restErr)
}

```

### Get Cards

```go
client := ankiconnect.NewClient()

// Get the Card Ids of cards due today
nodeIds, restErr := client.Cards.Get("prop:due=0")
if restErr != nil {
log.Fatal(restErr)
}

// Get the Card data of cards due today
notes, restErr := client.Cards.Get("prop:due=0")
if restErr != nil {
log.Fatal(restErr)
}

```

### Sync local data to Anki Cloud
```go
client := ankiconnect.NewClient()
Expand All @@ -97,4 +135,4 @@ restErr := client.Sync.Trigger()
if restErr != nil {
log.Fatal(restErr)
}
```
```
69 changes: 69 additions & 0 deletions cards.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package ankiconnect

import "github.com/privatesquare/bkst-go-utils/utils/errors"

const (
ActionFindCards = "findCards"
ActionCardsInfo = "cardsInfo"
)

type (
// Notes manager describes the interface that can be used to perform operation on the notes in a deck.
CardsManager interface {
Search(query string) (*[]int64, *errors.RestErr)
Get(query string) (*[]ResultCardsInfo, *errors.RestErr)
}

// notesManager implements NotesManager.
cardsManager struct {
Client *Client
}

ParamsFindCards struct {
Query string `json:"query,omitempty"`
}

ResultCardsInfo struct {
Answer string `json:"answer,omitempty"`
Question string `json:"question,omitempty"`
DeckName string `json:"deckName,omitempty"`
ModelName string `json:"modelName,omitempty"`
FieldOrder int64 `json:"fieldOrder,omitempty"`
Fields map[string]FieldData `json:"fields,omitempty"`
Css string `json:"css,omitempty"`
CardId int64 `json:"cardId,omitempty"`
Interval int64 `json:"interval,omitempty"`
Note int64 `json:"note,omitempty"`
Ord int64 `json:"ord,omitempty"`
Type int64 `json:"type,omitempty"`
Queue int64 `json:"queue,omitempty"`
Due int64 `json:"due,omitempty"`
Reps int64 `json:"reps,omitempty"`
Lapses int64 `json:"lapses,omitempty"`
Left int64 `json:"left,omitempty"`
Mod int64 `json:"mod,omitempty"`
}

// ParamsCardsInfo represents the ankiconnect API params for getting card info.
ParamsCardsInfo struct {
Cards *[]int64 `json:"cards,omitempty"`
}
)

func (cm *cardsManager) Search(query string) (*[]int64, *errors.RestErr) {
findParams := ParamsFindCards{
Query: query,
}
return post[[]int64](cm.Client, ActionFindCards, &findParams)
}

func (cm *cardsManager) Get(query string) (*[]ResultCardsInfo, *errors.RestErr) {
cardIds, restErr := cm.Search(query)
if restErr != nil {
return nil, restErr
}
infoParams := ParamsCardsInfo{
Cards: cardIds,
}
return post[[]ResultCardsInfo](cm.Client, ActionCardsInfo, &infoParams)
}
63 changes: 63 additions & 0 deletions cards_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package ankiconnect

import (
"net/http"
"testing"

"github.com/jarcoal/httpmock"
"github.com/stretchr/testify/assert"
)

func TestCardsManager_Get(t *testing.T) {
findCardsPayload := []byte(`{
"action": "findCards",
"version": 6,
"params": {
"query": "deck:current"
}
}`)

cardsInfoPayload := []byte(`{
"action": "cardsInfo",
"version": 6,
"params": {
"cards": [1498938915662, 1502098034048]
}
}`)

t.Run("success", func(t *testing.T) {
defer httpmock.Reset()

registerMultipleVerifiedPayloads(t,
[][2][]byte{
// Get will do two api calls, first findCards to get the card id's
{
findCardsPayload,
loadTestResult(t, ActionFindCards),
},
// Then cardsInfo to transform those into actual anki cards
{
cardsInfoPayload,
loadTestResult(t, ActionCardsInfo),
},
})

payload := "deck:current"
notes, restErr := client.Cards.Get(payload)
assert.Nil(t, restErr)
assert.Equal(t, len(*notes), 2)

})

t.Run("errorFailSearch", func(t *testing.T) {
defer httpmock.Reset()

registerErrorResponse(t)

_, restErr := client.Cards.Get("deck:current")
assert.NotNil(t, restErr)
assert.Equal(t, http.StatusBadRequest, restErr.StatusCode)
assert.Equal(t, "some error message", restErr.Message)
})

}
12 changes: 9 additions & 3 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,12 @@ type (
httpClient *resty.Client

// supported interfaces
Decks DecksManager
Notes NotesManager
Sync SyncManager
Decks DecksManager
Notes NotesManager
Sync SyncManager
Cards CardsManager
Media MediaManager
Models ModelsManager
}

// RequestPayload represents the request payload for anki connect api.
Expand Down Expand Up @@ -60,6 +63,9 @@ func NewClient() *Client {
c.Decks = &decksManager{Client: c}
c.Notes = &notesManager{Client: c}
c.Sync = &syncManager{Client: c}
c.Cards = &cardsManager{Client: c}
c.Media = &mediaManager{Client: c}
c.Models = &modelsManager{Client: c}

return c
}
Expand Down
16 changes: 4 additions & 12 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,6 @@ const (
ankiConnectTestVersion = 2
)

var (
client = NewClient()
)

func TestClient_NewClient(t *testing.T) {
c := NewClient()
assert.NotNil(t, c)
Expand Down Expand Up @@ -61,24 +57,20 @@ func TestClient_SetSyncManager(t *testing.T) {

func TestClient_Ping(t *testing.T) {
t.Run("success", func(t *testing.T) {
c := NewClient()
httpmock.ActivateNonDefault(c.httpClient.GetClient())
defer httpmock.DeactivateAndReset()
defer httpmock.Reset()

responder, err := httpmock.NewJsonResponder(http.StatusOK, "")
assert.NoError(t, err)
httpmock.RegisterResponder(http.MethodGet, ankiConnectUrl, responder)

restErr := c.Ping()
restErr := client.Ping()
assert.Nil(t, restErr)
})

t.Run("failure", func(t *testing.T) {
c := NewClient()
httpmock.ActivateNonDefault(c.httpClient.GetClient())
defer httpmock.DeactivateAndReset()
defer httpmock.Reset()

restErr := c.Ping()
restErr := client.Ping()
assert.NotNil(t, restErr)
assert.Equal(t, http.StatusServiceUnavailable, restErr.StatusCode)
assert.Equal(t, ankiConnectPingErrMsg, restErr.Message)
Expand Down
2 changes: 1 addition & 1 deletion data/test/addNotePayload.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,4 @@
}]
}
}
}
}
4 changes: 0 additions & 4 deletions data/test/addNoteResult.json

This file was deleted.

50 changes: 50 additions & 0 deletions data/test/cardsInfoResult.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"result": [
{
"answer": "back content",
"question": "front content",
"deckName": "Default",
"modelName": "Basic",
"fieldOrder": 1,
"fields": {
"Front": {"value": "front content", "order": 0},
"Back": {"value": "back content", "order": 1}
},
"css":"p {font-family:Arial;}",
"cardId": 1498938915662,
"interval": 16,
"note":1502298033753,
"ord": 1,
"type": 0,
"queue": 0,
"due": 1,
"reps": 1,
"lapses": 0,
"left": 6,
"mod": 1629454092
},
{
"answer": "back content",
"question": "front content",
"deckName": "Default",
"modelName": "Basic",
"fieldOrder": 0,
"fields": {
"Front": {"value": "front content", "order": 0},
"Back": {"value": "back content", "order": 1}
},
"css":"p {font-family:Arial;}",
"cardId": 1502098034048,
"interval": 23,
"note":1502298033753,
"ord": 1,
"type": 0,
"queue": 0,
"due": 1,
"reps": 1,
"lapses": 0,
"left": 6
}
],
"error": null
}
4 changes: 0 additions & 4 deletions data/test/createDeck.json

This file was deleted.

58 changes: 58 additions & 0 deletions data/test/createModelExtraResult.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
{
"result": {
"id": 1677087866636,
"name": "fish",
"type": 0,
"mod": 1677087866,
"usn": -1,
"sortf": 0,
"did": null,
"tmpls": [
{
"name": "simple_fish_card",
"ord": 0,
"qfmt": "The fish is {{a}}",
"afmt": "The color is {{b}}",
"bqfmt": "",
"bafmt": "",
"did": null,
"bfont": "",
"bsize": 0
}
],
"flds": [
{
"name": "a",
"ord": 0,
"sticky": false,
"rtl": false,
"font": "Arial",
"size": 20,
"description": ""
},
{
"name": "b",
"ord": 1,
"sticky": false,
"rtl": false,
"font": "Arial",
"size": 20,
"description": ""
}
],
"css": ".card {\n font-family: arial;\n font-size: 20px;\n text-align: center;\n color: black;\n background-color: white;\n}\n",
"latexPre": "\\documentclass[12pt]{article}\n\\special{papersize=3in,5in}\n\\usepackage[utf8]{inputenc}\n\\usepackage{amssymb,amsmath}\n\\pagestyle{empty}\n\\setlength{\\parindent}{0in}\n\\begin{document}\n",
"latexPost": "\\end{document}",
"latexsvg": false,
"req": [
[
0,
"any",
[
0
]
]
]
},
"error": null
}
17 changes: 17 additions & 0 deletions data/test/createModelPayload.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"action": "createModel",
"version": 6,
"params": {
"modelName": "newModelName",
"inOrderFields": ["Field1", "Field2", "Field3"],
"css": "Optional CSS with default to builtin css",
"isCloze": false,
"cardTemplates": [
{
"Name": "My Card 1",
"Front": "Front html {{Field1}}",
"Back": "Back html {{Field2}}"
}
]
}
}
Loading

0 comments on commit 5fb4e18

Please sign in to comment.