Compare commits

...

18 Commits
3.20.0 ... main

Author SHA1 Message Date
cea9a47de4 Fix filtering to use queries rather than ad-hoc code 2025-12-23 05:35:20 -05:00
6a24cd6546 Add confirmation menu to removing cards as well 2025-12-22 20:10:11 -05:00
b58718b537 Adding cards now requires confirmation 2025-12-21 11:02:09 -05:00
c4b4967777 Slightly increase image size 2025-12-20 23:27:50 -05:00
3e4a977bef Add basic in-deck tracking 2025-12-20 23:09:45 -05:00
bfa34d5c06 Add initial image support via kitty terminal protocol 2025-12-20 22:00:11 -05:00
Florian Baumann
f75888a1a9 Adjustments for docker image to comply with update step 2025-12-03 08:53:02 +01:00
Florian Baumann
806137de61 Update to go 1.25 2025-12-03 08:11:28 +01:00
Florian Baumann
34686494dd Quit update on err 2025-12-03 08:06:53 +01:00
Florian Baumann
507eef148a Fetch updates from scryfall bulk file
* Switch update to bulkupdate
* Remove tmpfile
* Better output for update
2025-12-03 08:03:59 +01:00
Florian Baumann
a9d1fbc2cd Rename Id to ID 2025-10-17 11:31:34 +02:00
Florian Baumann
3c593f5fdc Remove unused unique flag from removeInteractive 2025-10-17 11:31:18 +02:00
Florian Baumann
7d23bca7f1 Rename count_reserved to countReserved 2025-10-17 11:29:19 +02:00
Florian Baumann
956ae53b59 Rename exportJson to exportJSON 2025-10-17 11:27:58 +02:00
Florian Baumann
7a1803df3a Rename count_all to countAll in stats.go 2025-10-17 11:27:48 +02:00
Florian Baumann
6e961a708c Add package comment 2025-10-17 11:25:32 +02:00
Florian Baumann
dab28a044a Fix export of foils 2025-04-04 09:15:04 +02:00
Florian Baumann
e2c7e54c16 New missing view 2025-03-23 12:05:37 +01:00
17 changed files with 445 additions and 72 deletions

View File

@ -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
View File

@ -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
View File

@ -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=

View File

@ -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)

View File

@ -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
View 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
}

View File

@ -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
@ -46,7 +46,7 @@ var exportCmd = &cobra.Command{
case "moxfield": case "moxfield":
exportMoxfield(cardList) exportMoxfield(cardList)
case "json": case "json":
exportJson(cardList) exportJSON(cardList)
} }
return nil return nil
}, },
@ -61,7 +61,7 @@ func exportTCGPowertools(cards []Card) {
fmt.Println("quantity,cardmarketId,name,set,condition,language,isFoil,isPlayset,price,comment") fmt.Println("quantity,cardmarketId,name,set,condition,language,isFoil,isPlayset,price,comment")
for _, card := range cards { for _, card := range cards {
fmt.Printf("%d,%.0f,%s,%s,EX,German,false,false,%.2f,\n", card.SerraCount, card.CardmarketID, card.Name, card.SetName, card.getValue(false)) fmt.Printf("%d,%.0f,%s,%s,EX,German,false,false,%.2f,\n", card.SerraCount+card.SerraCountFoil, card.CardmarketID, card.Name, card.SetName, card.getValue(false))
} }
} }
@ -77,7 +77,7 @@ func exportMoxfield(cards []Card) {
for _, card := range cards { for _, card := range cards {
records = append(records, records = append(records,
[]string{fmt.Sprintf("%d", card.SerraCount), card.Name, card.Set, "NM", "English", "FALSE", card.CollectorNumber, "FALSE", "FALSE", ""}) []string{fmt.Sprintf("%d", card.SerraCount+card.SerraCountFoil), card.Name, card.Set, "NM", "English", "FALSE", card.CollectorNumber, "FALSE", "FALSE", ""})
} }
for _, record := range records { for _, record := range records {
@ -105,7 +105,7 @@ func exportTCGHome(cards []Card) {
for _, card := range cards { for _, card := range cards {
records = append(records, records = append(records,
[]string{fmt.Sprintf("%d", card.SerraCount), card.Name, "", card.Set, card.CollectorNumber, "English", "EX", card.ID, ""}) []string{fmt.Sprintf("%d", card.SerraCount+card.SerraCountFoil), card.Name, "", card.Set, card.CollectorNumber, "English", "EX", card.ID, ""})
} }
for _, record := range records { for _, record := range records {
@ -121,7 +121,7 @@ func exportTCGHome(cards []Card) {
} }
} }
func exportJson(cards []Card) { func exportJSON(cards []Card) {
ehj, _ := json.MarshalIndent(cards, "", " ") ehj, _ := json.MarshalIndent(cards, "", " ")
fmt.Println(string(ehj)) fmt.Println(string(ehj))
} }

View File

@ -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'
}

View File

@ -76,7 +76,7 @@ cards you dont own (yet) :)`,
}) })
for _, card := range missingCards { for _, card := range missingCards {
fmt.Printf("%s%s/%s%s %s%.02f%s%s\t%s (%s)\n", Purple, card.Set, card.CollectorNumber, Reset, Green, card.getValue(false), Reset, getCurrency(), card.Name, card.SetName) fmt.Printf("%s%s/%s\t%s(%s, %shttps://scryfall.com/card/%s/%s%s)\t%s%.02f%s%s\t%s (%s)\n", Purple, card.Set, card.CollectorNumber, Reset, string([]rune(card.Rarity)[0]), Background, card.Set, card.CollectorNumber, Reset, Green, card.getValue(false), Reset, getCurrency(), card.Name, card.SetName)
} }
return nil return nil

View File

@ -26,7 +26,7 @@ var removeCmd = &cobra.Command{
RunE: func(cmd *cobra.Command, cards []string) error { RunE: func(cmd *cobra.Command, cards []string) error {
if interactive { if interactive {
removeCardsInteractive(unique, set) removeCardsInteractive(set)
} else { } else {
removeCards(cards, count) removeCards(cards, count)
} }
@ -34,7 +34,7 @@ var removeCmd = &cobra.Command{
}, },
} }
func removeCardsInteractive(unique bool, set string) { func removeCardsInteractive(set string) {
l := Logger() l := Logger()
if len(set) == 0 { if len(set) == 0 {
@ -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())

View File

@ -1,3 +1,7 @@
// Package serra
//
// It implements base functions and also cli wrappers
// The entire tool consists only of this one package.
package serra package serra
import ( import (
@ -13,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

View File

@ -2,11 +2,14 @@ package serra
import ( import (
"bytes" "bytes"
"encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"log" "log"
"net/http" "net/http"
"os"
"time" "time"
"go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/bson/primitive"
@ -14,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"`
@ -119,18 +126,119 @@ type Card struct {
Variation bool `json:"variation"` Variation bool `json:"variation"`
} }
// Getter for currency specific value type BulkIndex struct {
func (c Card) getValue(foil bool) float64 { Object string `json:"object"`
if getCurrency() == EUR { HasMore bool `json:"has_more"`
if foil { Data []struct {
return c.Prices.EurFoil Object string `json:"object"`
ID string `json:"id"`
Type string `json:"type"`
UpdatedAt time.Time `json:"updated_at"`
URI string `json:"uri"`
Name string `json:"name"`
Description string `json:"description"`
Size int `json:"size"`
DownloadURI string `json:"download_uri"`
ContentType string `json:"content_type"`
ContentEncoding string `json:"content_encoding"`
} `json:"data"`
}
func fetchBulkDownloadURL() (string, error) {
url := "https://api.scryfall.com/bulk-data"
downloadURL := ""
// Make an HTTP GET request
resp, err := http.Get(url)
if err != nil {
log.Fatalf("Error fetching data: %v", err)
}
defer resp.Body.Close()
// Read the response body
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatalf("Error reading response body: %v", err)
}
// Unmarshal the JSON response
var bulkData BulkIndex
if err := json.Unmarshal(body, &bulkData); err != nil {
log.Fatalf("Error unmarshaling JSON: %v", err)
}
// Find and print the unique cards URL
for _, item := range bulkData.Data {
if item.Type == "default_cards" {
downloadURL = item.DownloadURI
} }
return c.Prices.Eur
} }
if foil {
return c.Prices.UsdFoil return downloadURL, nil
}
func downloadBulkData(downloadURL string) (string, error) {
// Create a temporary directory
tempDir, err := os.MkdirTemp("", "download")
if err != nil {
log.Fatalf("Error creating temporary directory: %v", err)
} }
return c.Prices.Usd // defer os.RemoveAll(tempDir) // Clean up the directory when done
// Create a temporary file in the temporary directory
tempFile, err := os.CreateTemp(tempDir, "downloaded-*.json") // Adjust the extension if necessary
if err != nil {
log.Fatalf("Error creating temporary file: %v", err)
}
// defer tempFile.Close() // Ensure we close the file when we're done
// Download the file
resp, err := http.Get(downloadURL)
if err != nil {
log.Fatalf("Error downloading file: %v", err)
}
defer resp.Body.Close() // Make sure to close the response body
// Check for a successful response
if resp.StatusCode != http.StatusOK {
log.Fatalf("Error: received status code %d", resp.StatusCode)
}
// Copy the response body to the temporary file
_, err = io.Copy(tempFile, resp.Body)
if err != nil {
log.Fatalf("Error saving file: %v", err)
}
return tempFile.Name(), nil
}
func loadBulkFile(bulkFilePath string) ([]Card, error) {
var cards []Card
fileBytes, _ := os.ReadFile(bulkFilePath)
defer os.Remove(bulkFilePath)
err := json.Unmarshal(fileBytes, &cards)
if err != nil {
fmt.Println("Error unmarshalling bulk file:", err)
return cards, nil
}
return cards, nil
}
func getCardFromBulk(cards []Card, setName, collectorNumber string) (*Card, error) {
var foundCard Card
for _, v := range cards {
if v.CollectorNumber == collectorNumber && v.Set == setName {
foundCard = v
return &foundCard, nil
}
}
return &Card{}, fmt.Errorf("Card %s/%s not found in bulk data", setName, collectorNumber)
} }
type PriceEntry struct { type PriceEntry struct {
@ -170,6 +278,20 @@ type Set struct {
URI string `json:"uri"` URI string `json:"uri"`
} }
// Getter for currency specific value
func (c Card) getValue(foil bool) float64 {
if getCurrency() == EUR {
if foil {
return c.Prices.EurFoil
}
return c.Prices.Eur
}
if foil {
return c.Prices.UsdFoil
}
return c.Prices.Usd
}
func fetchCard(setName, collectorNumber string) (*Card, error) { func fetchCard(setName, collectorNumber string) (*Card, error) {
resp, err := http.Get(fmt.Sprintf("https://api.scryfall.com/cards/%s/%s/", setName, collectorNumber)) resp, err := http.Get(fmt.Sprintf("https://api.scryfall.com/cards/%s/%s/", setName, collectorNumber))
if err != nil { if err != nil {
@ -181,7 +303,7 @@ func fetchCard(setName, collectorNumber string) (*Card, error) {
return &Card{}, fmt.Errorf("Card %s/%s not found", setName, collectorNumber) return &Card{}, fmt.Errorf("Card %s/%s not found", setName, collectorNumber)
} }
//We Read the response body on the line below. //we read the response body on the line below.
body, err := io.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
log.Fatalf("%s", err) log.Fatalf("%s", err)
@ -191,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)
@ -204,11 +325,24 @@ 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
} }
func fetchSets() (*SetList, error) { func fetchSets() (*SetList, error) {
// TODO better URL Building... // TODO: better URL Building...
resp, err := http.Get("https://api.scryfall.com/sets") resp, err := http.Get("https://api.scryfall.com/sets")
if err != nil { if err != nil {
log.Fatalln(err) log.Fatalln(err)

View File

@ -92,7 +92,7 @@ func showValueStats(coll *Collection, totalcoll *Collection) {
l.Error(err) l.Error(err)
foilValue = 0 foilValue = 0
} }
count_all, err := getFloat64(stats[0]["count_all"]) countAll, err := getFloat64(stats[0]["count_all"])
if err != nil { if err != nil {
l.Error(err) l.Error(err)
foilValue = 0 foilValue = 0
@ -101,7 +101,7 @@ func showValueStats(coll *Collection, totalcoll *Collection) {
fmt.Printf("Total: %s%.2f%s%s\n", Pink, totalValue, getCurrency(), Reset) fmt.Printf("Total: %s%.2f%s%s\n", Pink, totalValue, getCurrency(), Reset)
fmt.Printf("Normal: %s%.2f%s%s\n", Pink, normalValue, getCurrency(), Reset) fmt.Printf("Normal: %s%.2f%s%s\n", Pink, normalValue, getCurrency(), Reset)
fmt.Printf("Foils: %s%.2f%s%s\n", Pink, foilValue, getCurrency(), Reset) fmt.Printf("Foils: %s%.2f%s%s\n", Pink, foilValue, getCurrency(), Reset)
fmt.Printf("Average Card: %s%.2f%s%s\n", Pink, totalValue/count_all, getCurrency(), Reset) fmt.Printf("Average Card: %s%.2f%s%s\n", Pink, totalValue/countAll, getCurrency(), Reset)
total, _ := totalcoll.storageFindTotal() total, _ := totalcoll.storageFindTotal()
fmt.Printf("History: \n") fmt.Printf("History: \n")
@ -120,11 +120,11 @@ func showReservedListStats(coll *Collection) {
}}}, }}},
}) })
var count_reserved int32 var countReserved int32
if len(reserved) > 0 { if len(reserved) > 0 {
count_reserved = reserved[0]["count"].(int32) countReserved = reserved[0]["count"].(int32)
} }
fmt.Printf("Reserved List: %s%d%s\n", Yellow, count_reserved, Reset) fmt.Printf("Reserved List: %s%d%s\n", Yellow, countReserved, Reset)
} }
func showRarityStats(coll *Collection) { func showRarityStats(coll *Collection) {
@ -150,7 +150,7 @@ func showRarityStats(coll *Collection) {
func showCardsAddedPerMonth(coll *Collection) { func showCardsAddedPerMonth(coll *Collection) {
fmt.Printf("\n%sCards added over time%s\n", Green, Reset) fmt.Printf("\n%sCards added over time%s\n", Green, Reset)
type Caot struct { type Caot struct {
Id struct { ID struct {
Year int32 `mapstructure:"year"` Year int32 `mapstructure:"year"`
Month int32 `mapstructure:"month"` Month int32 `mapstructure:"month"`
} `mapstructure:"_id"` } `mapstructure:"_id"`
@ -178,7 +178,7 @@ func showCardsAddedPerMonth(coll *Collection) {
for _, mo := range caot { for _, mo := range caot {
moo := new(Caot) moo := new(Caot)
mapstructure.Decode(mo, moo) mapstructure.Decode(mo, moo)
fmt.Printf("%d-%02d: %s%d%s\n", moo.Id.Year, moo.Id.Month, Purple, moo.Count, Reset) fmt.Printf("%d-%02d: %s%d%s\n", moo.ID.Year, moo.ID.Month, Purple, moo.Count, Reset)
} }
} }

View File

@ -17,7 +17,8 @@ type Total struct {
Value []PriceEntry `bson:"value"` Value []PriceEntry `bson:"value"`
} }
// https://siongui.github.io/2017/02/11/go-add-method-function-to-type-in-external-package/ // Collection Struct
// reason: https://siongui.github.io/2017/02/11/go-add-method-function-to-type-in-external-package/
type Collection struct { type Collection struct {
*mongo.Collection *mongo.Collection
} }

23
pkg/serra/undeck.go Normal file
View 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
},
}

View File

@ -19,8 +19,8 @@ func init() {
var updateCmd = &cobra.Command{ var updateCmd = &cobra.Command{
Aliases: []string{"u"}, Aliases: []string{"u"},
Use: "update", Use: "update",
Short: "Update card values from scryfall", Short: "update card values from scryfall",
Long: `The update mechanism iterates over each card in your collection and fetches its price. After all cards you own in a set are updated, the set value will update. After all Sets are updated, the whole collection value is updated.`, Long: `the update mechanism iterates over each card in your collection and fetches its price. after all cards you own in a set are updated, the set value will update. after all sets are updated, the whole collection value is updated.`,
SilenceErrors: true, SilenceErrors: true,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
@ -49,11 +49,35 @@ var updateCmd = &cobra.Command{
{"usdfoil", bson.D{{"$sum", bson.D{{"$multiply", bson.A{"$last_price.usd_foil", "$serra_count_foil"}}}}}}, {"usdfoil", bson.D{{"$sum", bson.D{{"$multiply", bson.A{"$last_price.usd_foil", "$serra_count_foil"}}}}}},
}}} }}}
l.Info("Fetching bulk data from scryfall...")
downloadURL, err := fetchBulkDownloadURL()
if err != nil {
l.Error("Could not extract bulk download URL:", err)
return err
}
l.Infof("Found latest bulkfile url: %s", downloadURL)
l.Info("Downloading bulk data file...")
bulkFilePath, err := downloadBulkData(downloadURL)
if err != nil {
l.Error("Could not fetch bulk json from scryfall", err)
return err
}
l.Info("Loading bulk data file...")
updatedCards, err := loadBulkFile(bulkFilePath)
if err != nil {
l.Error("Could not load bulk file:", err)
return err
}
l.Infof("Successfully loaded %d cards. Starting Update.", len(updatedCards))
sets, _ := fetchSets() sets, _ := fetchSets()
for _, set := range sets.Data { for _, set := range sets.Data {
// When downloading new sets, PriceList needs to be initialized // When downloading new sets, PriceList needs to be initialized
// This query silently fails if set was already downloaded. Not nice but ok for now. // This query silently fails if set was already downloaded. Not nice but ok for now.
// TODO: make this not fail silently
set.SerraPrices = []PriceEntry{} set.SerraPrices = []PriceEntry{}
setscoll.storageAddSet(&set) setscoll.storageAddSet(&set)
@ -74,13 +98,13 @@ var updateCmd = &cobra.Command{
SaucerHead: "[green]>[reset]", SaucerHead: "[green]>[reset]",
SaucerPadding: " ", SaucerPadding: " ",
BarStart: "|", BarStart: "|",
BarEnd: set.Name, BarEnd: "| " + set.Name,
}), }),
) )
for _, card := range cards { for _, card := range cards {
bar.Add(1) bar.Add(1)
updatedCard, err := fetchCard(card.Set, card.CollectorNumber) updatedCard, err := getCardFromBulk(updatedCards, card.Set, card.CollectorNumber)
if err != nil { if err != nil {
l.Error(err) l.Error(err)
continue continue
@ -115,7 +139,6 @@ var updateCmd = &cobra.Command{
"$set": bson.M{"serra_updated": p.Date, "cardcount": set.CardCount}, "$set": bson.M{"serra_updated": p.Date, "cardcount": set.CardCount},
"$push": bson.M{"serra_prices": p}, "$push": bson.M{"serra_prices": p},
} }
// fmt.Printf("Set %s%s%s (%s) is now worth %s%.02f EUR%s\n", Pink, set.Name, Reset, set.Code, Yellow, setvalue[0]["value"], Reset)
setscoll.storageUpdate(bson.M{"code": bson.M{"$eq": set.Code}}, setUpdate) setscoll.storageUpdate(bson.M{"code": bson.M{"$eq": set.Code}}, setUpdate)
} }
@ -130,7 +153,7 @@ var updateCmd = &cobra.Command{
tmpCard := Card{} tmpCard := Card{}
tmpCard.Prices = t tmpCard.Prices = t
fmt.Printf("\n%sUpdating total value of collection to: %s%.02f%s%s\n", Green, Yellow, tmpCard.getValue(false)+tmpCard.getValue(true), getCurrency(), Reset) l.Info("\n%sUpdating total value of collection to: %s%.02f%s%s\n", Green, Yellow, tmpCard.getValue(false)+tmpCard.getValue(true), getCurrency(), Reset)
totalcoll.storageAddTotal(t) totalcoll.storageAddTotal(t)
return nil return nil

View File

@ -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{}