Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cea9a47de4 | |||
| 6a24cd6546 | |||
| b58718b537 | |||
| c4b4967777 | |||
| 3e4a977bef | |||
| bfa34d5c06 | |||
|
|
f75888a1a9 | ||
|
|
806137de61 |
@ -1,6 +1,6 @@
|
|||||||
FROM golang:1.21-alpine AS builder
|
FROM golang:1.25-alpine AS build
|
||||||
|
|
||||||
RUN apk update && apk add --no-cache git
|
RUN apk update && apk add --no-cache git ca-certificates curl
|
||||||
|
|
||||||
WORKDIR /go/src/app
|
WORKDIR /go/src/app
|
||||||
COPY pkg /go/src/app/pkg
|
COPY pkg /go/src/app/pkg
|
||||||
@ -16,7 +16,9 @@ RUN go build -ldflags "-X github.com/noqqe/serra/pkg/serra.Version=`git describe
|
|||||||
# copy
|
# copy
|
||||||
FROM scratch
|
FROM scratch
|
||||||
WORKDIR /go/src/app
|
WORKDIR /go/src/app
|
||||||
COPY --from=builder /go/src/app/serra /go/src/app/serra
|
COPY --from=build /go/src/app/serra /go/src/app/serra
|
||||||
|
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||||
|
COPY --from=build /tmp /tmp
|
||||||
COPY templates /go/src/app/templates
|
COPY templates /go/src/app/templates
|
||||||
|
|
||||||
# run
|
# run
|
||||||
|
|||||||
10
go.mod
10
go.mod
@ -1,8 +1,8 @@
|
|||||||
module github.com/noqqe/serra
|
module github.com/noqqe/serra
|
||||||
|
|
||||||
go 1.22.0
|
go 1.25.0
|
||||||
|
|
||||||
toolchain go1.22.6
|
toolchain go1.25.4
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/charmbracelet/log v0.4.0
|
github.com/charmbracelet/log v0.4.0
|
||||||
@ -22,6 +22,7 @@ require (
|
|||||||
github.com/charmbracelet/x/ansi v0.3.2 // indirect
|
github.com/charmbracelet/x/ansi v0.3.2 // indirect
|
||||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||||
|
github.com/dolmen-go/kittyimg v0.0.0-20250610224728-874967bd8ea4 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.6 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.6 // indirect
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||||
@ -43,6 +44,7 @@ require (
|
|||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||||
github.com/muesli/termenv v0.15.2 // indirect
|
github.com/muesli/termenv v0.15.2 // indirect
|
||||||
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
@ -57,8 +59,8 @@ require (
|
|||||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect
|
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect
|
||||||
golang.org/x/net v0.30.0 // indirect
|
golang.org/x/net v0.30.0 // indirect
|
||||||
golang.org/x/sync v0.8.0 // indirect
|
golang.org/x/sync v0.8.0 // indirect
|
||||||
golang.org/x/sys v0.26.0 // indirect
|
golang.org/x/sys v0.30.0 // indirect
|
||||||
golang.org/x/term v0.25.0 // indirect
|
golang.org/x/term v0.29.0 // indirect
|
||||||
golang.org/x/text v0.19.0 // indirect
|
golang.org/x/text v0.19.0 // indirect
|
||||||
google.golang.org/protobuf v1.35.1 // indirect
|
google.golang.org/protobuf v1.35.1 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
|||||||
8
go.sum
8
go.sum
@ -27,6 +27,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t
|
|||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dolmen-go/kittyimg v0.0.0-20250610224728-874967bd8ea4 h1:KGRb+vxMx5pGsfDjDSW2Th+b2OEflb0yC3s0daCmiYU=
|
||||||
|
github.com/dolmen-go/kittyimg v0.0.0-20250610224728-874967bd8ea4/go.mod h1:2vk7ATPVcI7uW4Sh6PrSQvtO+Czmq8509xcg/y8Osd0=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc=
|
github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc=
|
github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc=
|
||||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
@ -81,6 +83,8 @@ github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8
|
|||||||
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
||||||
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
|
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
|
||||||
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
|
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
|
||||||
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||||
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
@ -148,10 +152,14 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||||
|
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
|
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
|
||||||
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
|
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
|
||||||
|
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
||||||
|
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
|||||||
@ -141,11 +141,14 @@ func addCards(cards []string, unique bool, count int64) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
modifyCardCount(coll, &c, count, foil)
|
if askConfirmation(&c) {
|
||||||
|
modifyCardCount(coll, &c, count, foil)
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Fetch card from scryfall
|
// Fetch card from scryfall
|
||||||
c, err := fetchCard(setName, collectorNumber)
|
c, err := fetchCard(setName, collectorNumber)
|
||||||
|
|
||||||
outputColor := coloredValue(c.getValue(foil))
|
outputColor := coloredValue(c.getValue(foil))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Warn(err)
|
l.Warn(err)
|
||||||
@ -156,11 +159,18 @@ func addCards(cards []string, unique bool, count int64) error {
|
|||||||
var total int64 = 0
|
var total int64 = 0
|
||||||
if foil {
|
if foil {
|
||||||
c.SerraCountFoil = count
|
c.SerraCountFoil = count
|
||||||
|
c.SerraCountFoilDeck = 0
|
||||||
total = c.SerraCountFoil
|
total = c.SerraCountFoil
|
||||||
} else {
|
} else {
|
||||||
c.SerraCount = count
|
c.SerraCount = count
|
||||||
|
c.SerraCountDeck = 0
|
||||||
total = c.SerraCount
|
total = c.SerraCount
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !askConfirmation(c) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
err = coll.storageAdd(c)
|
err = coll.storageAdd(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Warn(err)
|
l.Warn(err)
|
||||||
|
|||||||
@ -1,10 +1,16 @@
|
|||||||
package serra
|
package serra
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"image/png"
|
||||||
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/dolmen-go/kittyimg"
|
||||||
|
"github.com/nfnt/resize"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"go.mongodb.org/mongo-driver/bson"
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
)
|
)
|
||||||
@ -23,6 +29,8 @@ func init() {
|
|||||||
cardCmd.Flags().BoolVarP(&detail, "detail", "d", false, "Show details for cards (url)")
|
cardCmd.Flags().BoolVarP(&detail, "detail", "d", false, "Show details for cards (url)")
|
||||||
cardCmd.Flags().BoolVarP(&reserved, "reserved", "w", false, "If card is on reserved list")
|
cardCmd.Flags().BoolVarP(&reserved, "reserved", "w", false, "If card is on reserved list")
|
||||||
cardCmd.Flags().BoolVarP(&foil, "foil", "f", false, "If card is foil list")
|
cardCmd.Flags().BoolVarP(&foil, "foil", "f", false, "If card is foil list")
|
||||||
|
cardCmd.Flags().BoolVarP(&drawImg, "image", "g", false, "Draw card image using kitty format")
|
||||||
|
cardCmd.Flags().BoolVarP(&omitInDeck, "omit-in-deck", "q", false, "Omit cards that are in decks")
|
||||||
rootCmd.AddCommand(cardCmd)
|
rootCmd.AddCommand(cardCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,7 +44,7 @@ otherwise you'll get a list of cards as a search result.`,
|
|||||||
SilenceErrors: true,
|
SilenceErrors: true,
|
||||||
RunE: func(cmd *cobra.Command, cards []string) error {
|
RunE: func(cmd *cobra.Command, cards []string) error {
|
||||||
if len(cards) == 0 {
|
if len(cards) == 0 {
|
||||||
cardList := Cards(rarity, set, sortby, name, oracle, cardType, reserved, foil, 0, 0)
|
cardList := Cards(rarity, set, sortby, name, oracle, cardType, reserved, foil, 0, 0, omitInDeck)
|
||||||
showCardList(cardList, detail)
|
showCardList(cardList, detail)
|
||||||
} else {
|
} else {
|
||||||
ShowCard(cards)
|
ShowCard(cards)
|
||||||
@ -65,7 +73,7 @@ func ShowCard(cardids []string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Cards(rarity, set, sortby, name, oracle, cardType string, reserved, foil bool, skip, limit int64) []Card {
|
func Cards(rarity, set, sortby, name, oracle, cardType string, reserved, foil bool, skip, limit int64, omitInDeck bool) []Card {
|
||||||
client := storageConnect()
|
client := storageConnect()
|
||||||
coll := &Collection{client.Database("serra").Collection("cards")}
|
coll := &Collection{client.Database("serra").Collection("cards")}
|
||||||
defer storageDisconnect(client)
|
defer storageDisconnect(client)
|
||||||
@ -138,6 +146,13 @@ func Cards(rarity, set, sortby, name, oracle, cardType string, reserved, foil bo
|
|||||||
filter = append(filter, bson.E{"serra_count_foil", bson.D{{"$gt", 0}}})
|
filter = append(filter, bson.E{"serra_count_foil", bson.D{{"$gt", 0}}})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if omitInDeck {
|
||||||
|
filter = append(filter, bson.E{ "$expr", bson.D{{"$or",bson.A{
|
||||||
|
bson.D{{"$ne", bson.A{"$serra_count", "$serra_count_deck"}}},
|
||||||
|
bson.D{{"$ne", bson.A{"$serra_count_foil", "$serra_count_foil_deck"}}},
|
||||||
|
}}}})
|
||||||
|
}
|
||||||
|
|
||||||
cards, _ := coll.storageFind(filter, sortStage, skip, limit)
|
cards, _ := coll.storageFind(filter, sortStage, skip, limit)
|
||||||
|
|
||||||
// This is needed because collectornumbers are strings (ie. "23a") but still we
|
// This is needed because collectornumbers are strings (ie. "23a") but still we
|
||||||
@ -165,36 +180,64 @@ func Cards(rarity, set, sortby, name, oracle, cardType string, reserved, foil bo
|
|||||||
func showCardList(cards []Card, detail bool) {
|
func showCardList(cards []Card, detail bool) {
|
||||||
|
|
||||||
var total float64
|
var total float64
|
||||||
if detail {
|
if drawImg {
|
||||||
|
for _, card := range cards {
|
||||||
|
drawImage(&card)
|
||||||
|
}
|
||||||
|
} else if detail {
|
||||||
for _, card := range cards {
|
for _, card := range cards {
|
||||||
fmt.Printf("* %dx %s%s%s (%s/%s) %s%.2f%s %s %s %s\n", card.SerraCount+card.SerraCountFoil+card.SerraCountEtched, Purple, card.Name, Reset, card.Set, card.CollectorNumber, Yellow, card.getValue(false), getCurrency(), Background, strings.Replace(card.ScryfallURI, "?utm_source=api", "", 1), Reset)
|
fmt.Printf("* %dx %s%s%s (%s/%s) %s%.2f%s %s %s %s\n", card.SerraCount+card.SerraCountFoil+card.SerraCountEtched, Purple, card.Name, Reset, card.Set, card.CollectorNumber, Yellow, card.getValue(false), getCurrency(), Background, strings.Replace(card.ScryfallURI, "?utm_source=api", "", 1), Reset)
|
||||||
total = total + card.getValue(false)*float64(card.SerraCount) + card.getValue(true)*float64(card.SerraCountFoil)
|
total = total + card.getValue(false)*float64(card.SerraCount) + card.getValue(true)*float64(card.SerraCountFoil)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for _, card := range cards {
|
for _, card := range cards {
|
||||||
fmt.Printf("* %dx %s%s%s (%s/%s) %s%.2f%s%s\n", card.SerraCount+card.SerraCountFoil+card.SerraCountEtched, Purple, card.Name, Reset, card.Set, card.CollectorNumber, Yellow, card.getValue(false), getCurrency(), Reset)
|
fmt.Printf("* %dx (%dx) %s%s%s (%s/%s) %s%.2f%s%s\n",
|
||||||
|
card.SerraCount+card.SerraCountFoil+card.SerraCountEtched-card.SerraCountDeck-card.SerraCountFoilDeck-card.SerraCountEtchedDeck,
|
||||||
|
card.SerraCount+card.SerraCountFoil+card.SerraCountEtched,
|
||||||
|
Purple, card.Name, Reset, card.Set, card.CollectorNumber, Yellow, card.getValue(false), getCurrency(), Reset)
|
||||||
total = total + card.getValue(false)*float64(card.SerraCount) + card.getValue(true)*float64(card.SerraCountFoil)
|
total = total + card.getValue(false)*float64(card.SerraCount) + card.getValue(true)*float64(card.SerraCountFoil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("\nTotal Value: %s%.2f%s%s\n", Yellow, total, getCurrency(), Reset)
|
if !drawImg {
|
||||||
|
fmt.Printf("\nTotal Value: %s%.2f%s%s\n", Yellow, total, getCurrency(), Reset)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func showCardDetails(card *Card) error {
|
func showCardDetails(card *Card) error {
|
||||||
fmt.Printf("%s%s%s (%s/%s)\n", Purple, card.Name, Reset, card.Set, card.CollectorNumber)
|
if drawImg {
|
||||||
fmt.Printf("Added: %s\n", stringToTime(card.SerraCreated))
|
drawImage(card)
|
||||||
fmt.Printf("Rarity: %s\n", card.Rarity)
|
} else {
|
||||||
fmt.Printf("Scryfall: %s\n", strings.Replace(card.ScryfallURI, "?utm_source=api", "", 1))
|
fmt.Printf("%s%s%s (%s/%s)\n", Purple, card.Name, Reset, card.Set, card.CollectorNumber)
|
||||||
|
fmt.Printf("Available: %d / %d\n", card.SerraCount + card.SerraCountFoil - card.SerraCountDeck - card.SerraCountFoilDeck, card.SerraCount + card.SerraCountFoil)
|
||||||
|
fmt.Printf("Added: %s\n", stringToTime(card.SerraCreated))
|
||||||
|
fmt.Printf("Rarity: %s\n", card.Rarity)
|
||||||
|
fmt.Printf("Scryfall: %s\n", strings.Replace(card.ScryfallURI, "?utm_source=api", "", 1))
|
||||||
|
|
||||||
fmt.Printf("\n%sCurrent Value%s\n", Green, Reset)
|
fmt.Printf("\n%sCurrent Value%s\n", Green, Reset)
|
||||||
fmt.Printf("* Normal: %dx %s%.2f%s%s\n", card.SerraCount, Yellow, card.getValue(false), getCurrency(), Reset)
|
fmt.Printf("* Normal: %dx %s%.2f%s%s\n", card.SerraCount, Yellow, card.getValue(false), getCurrency(), Reset)
|
||||||
if card.SerraCountFoil > 0 {
|
if card.SerraCountFoil > 0 {
|
||||||
fmt.Printf("* Foil: %dx %s%.2f%s%s\n", card.SerraCountFoil, Yellow, card.getValue(true), getCurrency(), Reset)
|
fmt.Printf("* Foil: %dx %s%.2f%s%s\n", card.SerraCountFoil, Yellow, card.getValue(true), getCurrency(), Reset)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("\n%sValue History%s\n", Green, Reset)
|
||||||
|
showPriceHistory(card.SerraPrices, "* ", false)
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("\n%sValue History%s\n", Green, Reset)
|
|
||||||
showPriceHistory(card.SerraPrices, "* ", false)
|
|
||||||
fmt.Println()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func drawImage(card *Card) {
|
||||||
|
fmt.Printf("%s - %s (%s/%s)\n", card.Name, card.SetName, card.Set, card.CollectorNumber)
|
||||||
|
data, err := base64.StdEncoding.DecodeString(card.SerraImage64)
|
||||||
|
if err == nil {
|
||||||
|
reader := bytes.NewReader(data)
|
||||||
|
img, err := png.Decode(reader)
|
||||||
|
img = resize.Resize(305, 425, img, resize.Bicubic)
|
||||||
|
if err == nil {
|
||||||
|
kittyimg.Fprintln(os.Stdout, img)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|||||||
67
pkg/serra/deck.go
Normal file
67
pkg/serra/deck.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
package serra
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
deckCmd.Flags().Int64VarP(&count, "count", "c", 1, "Amount of cards to add")
|
||||||
|
deckCmd.Flags().BoolVarP(&foil, "foil", "f", false, "Add foil variant of card")
|
||||||
|
rootCmd.AddCommand(deckCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
var deckCmd = &cobra.Command{
|
||||||
|
Aliases: []string{"d"},
|
||||||
|
Use: "deck",
|
||||||
|
Short: "Mark a card as in a deck",
|
||||||
|
Long: "Mark a card as in a deck",
|
||||||
|
SilenceErrors: true,
|
||||||
|
RunE: func(cmd *cobra.Command, cards []string) error {
|
||||||
|
deckCards(cards, count)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func deckCards(cards []string, count int64) error {
|
||||||
|
client := storageConnect()
|
||||||
|
coll := &Collection{client.Database("serra").Collection("cards")}
|
||||||
|
l := Logger()
|
||||||
|
defer storageDisconnect(client)
|
||||||
|
|
||||||
|
// Loop over different cards
|
||||||
|
for _, card := range cards {
|
||||||
|
// Extract collector number and set name from card input & trim any leading 0 from collector number
|
||||||
|
|
||||||
|
if !strings.Contains(card, "/") {
|
||||||
|
l.Errorf("Invalid card format %s. Needs to be set/collector number i.e. \"usg/13\"", card)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
setName := strings.ToLower(strings.Split(card, "/")[0])
|
||||||
|
collectorNumber := strings.TrimLeft(strings.Split(card, "/")[1], "0")
|
||||||
|
|
||||||
|
if collectorNumber == "" {
|
||||||
|
l.Errorf("Invalid card format %s. Needs to be set/collector number i.e. \"usg/13\"", card)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if card is already in collection
|
||||||
|
co, err := coll.storageFind(bson.D{{"set", setName}, {"collectornumber", collectorNumber}}, bson.D{}, 0, 0)
|
||||||
|
if err != nil {
|
||||||
|
l.Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(co) >= 1 {
|
||||||
|
modifyCardDeckCount(coll, &co[0], count, foil)
|
||||||
|
} else {
|
||||||
|
l.Errorf("Card not in collection: %s", card)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
storageDisconnect(client)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@ -25,7 +25,7 @@ var exportCmd = &cobra.Command{
|
|||||||
Supports multiple output formats depending on where you want to export your collection.`,
|
Supports multiple output formats depending on where you want to export your collection.`,
|
||||||
SilenceErrors: true,
|
SilenceErrors: true,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
cardList := Cards(rarity, set, sortby, name, oracle, cardType, reserved, foil, 0, 0)
|
cardList := Cards(rarity, set, sortby, name, oracle, cardType, reserved, foil, 0, 0, false)
|
||||||
|
|
||||||
// filter out cards that do not reach the minimum amount (--min-count)
|
// filter out cards that do not reach the minimum amount (--min-count)
|
||||||
// this is done after query result because find query constructed does not support
|
// this is done after query result because find query constructed does not support
|
||||||
|
|||||||
@ -41,17 +41,25 @@ func Logger() *log.Logger {
|
|||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
|
|
||||||
func modifyCardCount(coll *Collection, c *Card, amount int64, foil bool) error {
|
func getStoredCard(coll *Collection, c *Card) (Card, error) {
|
||||||
|
|
||||||
// find already existing card
|
// find already existing card
|
||||||
sort := bson.D{{"_id", 1}}
|
sort := bson.D{{"_id", 1}}
|
||||||
searchFilter := bson.D{{"_id", c.ID}}
|
searchFilter := bson.D{{"_id", c.ID}}
|
||||||
l := Logger()
|
|
||||||
storedCards, err := coll.storageFind(searchFilter, sort, 0, 0)
|
storedCards, err := coll.storageFind(searchFilter, sort, 0, 0)
|
||||||
|
if err != nil {
|
||||||
|
return Card{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return storedCards[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func modifyCardCount(coll *Collection, c *Card, amount int64, foil bool) error {
|
||||||
|
l := Logger()
|
||||||
|
|
||||||
|
storedCard, err := getStoredCard(coll, c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
storedCard := storedCards[0]
|
|
||||||
|
|
||||||
// update card amount
|
// update card amount
|
||||||
var update bson.M
|
var update bson.M
|
||||||
@ -87,6 +95,35 @@ func modifyCardCount(coll *Collection, c *Card, amount int64, foil bool) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func modifyCardDeckCount(coll *Collection, c *Card, amount int64, foil bool) error {
|
||||||
|
l := Logger()
|
||||||
|
|
||||||
|
storedCard, err := getStoredCard(coll, c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// update card amount
|
||||||
|
var update bson.M
|
||||||
|
if foil {
|
||||||
|
newAmount := min(max(storedCard.SerraCountFoilDeck + amount, 0), storedCard.SerraCountFoil)
|
||||||
|
l.Infof("%d / %d available", storedCard.SerraCountFoil - newAmount, storedCard.SerraCountFoil)
|
||||||
|
update = bson.M{
|
||||||
|
"$set": bson.M{"serra_count_foil_deck": newAmount},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newAmount := min(max(storedCard.SerraCountDeck + amount, 0), storedCard.SerraCount)
|
||||||
|
l.Infof("%d / %d available", storedCard.SerraCount - newAmount, storedCard.SerraCount)
|
||||||
|
update = bson.M{
|
||||||
|
"$set": bson.M{"serra_count_deck": newAmount},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
coll.storageUpdate(bson.M{"_id": bson.M{"$eq": c.ID}}, update)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func findCardByCollectorNumber(coll *Collection, setCode string, collectorNumber string) (*Card, error) {
|
func findCardByCollectorNumber(coll *Collection, setCode string, collectorNumber string) (*Card, error) {
|
||||||
sort := bson.D{{"_id", 1}}
|
sort := bson.D{{"_id", 1}}
|
||||||
searchFilter := bson.D{{"set", setCode}, {"collectornumber", collectorNumber}}
|
searchFilter := bson.D{{"set", setCode}, {"collectornumber", collectorNumber}}
|
||||||
@ -271,3 +308,16 @@ func coloredValue(value float64) string {
|
|||||||
|
|
||||||
return outputColor
|
return outputColor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func askConfirmation(card *Card) bool {
|
||||||
|
drawImage(card)
|
||||||
|
|
||||||
|
fmt.Println("Is this correct (y/n)?")
|
||||||
|
|
||||||
|
var char = 'x'
|
||||||
|
for char != 'y' && char != 'n' {
|
||||||
|
fmt.Scanf("%c\n", &char)
|
||||||
|
}
|
||||||
|
|
||||||
|
return char == 'y'
|
||||||
|
}
|
||||||
|
|||||||
@ -103,6 +103,10 @@ func removeCards(cards []string, count int64) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !askConfirmation(c) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if foil && c.SerraCountFoil == 1 && c.SerraCount == 0 || !foil && c.SerraCount == 1 && c.SerraCountFoil == 0 {
|
if foil && c.SerraCountFoil == 1 && c.SerraCount == 0 || !foil && c.SerraCount == 1 && c.SerraCountFoil == 0 {
|
||||||
coll.storageRemove(bson.M{"_id": c.ID})
|
coll.storageRemove(bson.M{"_id": c.ID})
|
||||||
l.Infof("\"%s\" (%.2f%s) removed", c.Name, c.getValue(foil), getCurrency())
|
l.Infof("\"%s\" (%.2f%s) removed", c.Name, c.getValue(foil), getCurrency())
|
||||||
|
|||||||
@ -17,11 +17,13 @@ var (
|
|||||||
cmc int64
|
cmc int64
|
||||||
count int64
|
count int64
|
||||||
detail bool
|
detail bool
|
||||||
|
drawImg bool
|
||||||
foil bool
|
foil bool
|
||||||
format string
|
format string
|
||||||
interactive bool
|
interactive bool
|
||||||
limit float64
|
limit float64
|
||||||
name string
|
name string
|
||||||
|
omitInDeck bool
|
||||||
oracle string
|
oracle string
|
||||||
port uint64
|
port uint64
|
||||||
rarity string
|
rarity string
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package serra
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@ -16,12 +17,16 @@ import (
|
|||||||
|
|
||||||
type Card struct {
|
type Card struct {
|
||||||
// Added by Serra
|
// Added by Serra
|
||||||
SerraCount int64 `bson:"serra_count"`
|
SerraCount int64 `bson:"serra_count"`
|
||||||
SerraCountFoil int64 `bson:"serra_count_foil"`
|
SerraCountFoil int64 `bson:"serra_count_foil"`
|
||||||
SerraCountEtched int64 `bson:"serra_count_etched"`
|
SerraCountEtched int64 `bson:"serra_count_etched"`
|
||||||
SerraPrices []PriceEntry `bson:"serra_prices"`
|
SerraCountDeck int64 `bson:"serra_count_deck"`
|
||||||
SerraCreated primitive.DateTime `bson:"serra_created"`
|
SerraCountFoilDeck int64 `bson:"serra_count_foil_deck"`
|
||||||
SerraUpdated primitive.DateTime `bson:"serra_updated"`
|
SerraCountEtchedDeck int64 `bson:"serra_count_etched_deck"`
|
||||||
|
SerraPrices []PriceEntry `bson:"serra_prices"`
|
||||||
|
SerraCreated primitive.DateTime `bson:"serra_created"`
|
||||||
|
SerraUpdated primitive.DateTime `bson:"serra_updated"`
|
||||||
|
SerraImage64 string `bson:"serra_image"`
|
||||||
|
|
||||||
Artist string `json:"artist"`
|
Artist string `json:"artist"`
|
||||||
ArtistIds []string `json:"artist_ids"`
|
ArtistIds []string `json:"artist_ids"`
|
||||||
@ -308,7 +313,6 @@ func fetchCard(setName, collectorNumber string) (*Card, error) {
|
|||||||
r := bytes.NewReader(body)
|
r := bytes.NewReader(body)
|
||||||
decoder := json.NewDecoder(r)
|
decoder := json.NewDecoder(r)
|
||||||
val := &Card{}
|
val := &Card{}
|
||||||
|
|
||||||
err = decoder.Decode(val)
|
err = decoder.Decode(val)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("%s", err)
|
log.Fatalf("%s", err)
|
||||||
@ -321,6 +325,19 @@ func fetchCard(setName, collectorNumber string) (*Card, error) {
|
|||||||
val.Prices.Date = primitive.NewDateTimeFromTime(time.Now())
|
val.Prices.Date = primitive.NewDateTimeFromTime(time.Now())
|
||||||
val.SerraPrices = append(val.SerraPrices, val.Prices)
|
val.SerraPrices = append(val.SerraPrices, val.Prices)
|
||||||
|
|
||||||
|
imgResp, imgErr := http.Get(val.ImageUris.Png)
|
||||||
|
if imgErr != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
return &Card{}, err
|
||||||
|
}
|
||||||
|
bytes, readErr := io.ReadAll(imgResp.Body)
|
||||||
|
if readErr != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
return &Card{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
val.SerraImage64 = base64.StdEncoding.EncodeToString(bytes)
|
||||||
|
|
||||||
return val, nil
|
return val, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
23
pkg/serra/undeck.go
Normal file
23
pkg/serra/undeck.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package serra
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
undeckCmd.Flags().Int64VarP(&count, "count", "c", 1, "Amount of cards to add")
|
||||||
|
undeckCmd.Flags().BoolVarP(&foil, "foil", "f", false, "Add foil variant of card")
|
||||||
|
rootCmd.AddCommand(undeckCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
var undeckCmd = &cobra.Command{
|
||||||
|
Aliases: []string{"u"},
|
||||||
|
Use: "undeck",
|
||||||
|
Short: "Unmark a card as in a deck",
|
||||||
|
Long: "Unmark a card as in a deck",
|
||||||
|
SilenceErrors: true,
|
||||||
|
RunE: func(cmd *cobra.Command, cards []string) error {
|
||||||
|
deckCards(cards, -count)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
@ -71,7 +71,7 @@ func landingPage(c *gin.Context) {
|
|||||||
sets := Sets("release")
|
sets := Sets("release")
|
||||||
|
|
||||||
// Fetch all results based on filter criteria
|
// Fetch all results based on filter criteria
|
||||||
cards := Cards("", query.Set, query.Sort, query.Name, "", "", false, false, query.Page*int64(limit), limit)
|
cards := Cards("", query.Set, query.Sort, query.Name, "", "", false, false, query.Page*int64(limit), limit, false)
|
||||||
|
|
||||||
// Construct quick way for counting results
|
// Construct quick way for counting results
|
||||||
filter := bson.D{}
|
filter := bson.D{}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user