Compare commits
87 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cea9a47de4 | |||
| 6a24cd6546 | |||
| b58718b537 | |||
| c4b4967777 | |||
| 3e4a977bef | |||
| bfa34d5c06 | |||
|
|
f75888a1a9 | ||
|
|
806137de61 | ||
|
|
34686494dd | ||
|
|
507eef148a | ||
|
|
a9d1fbc2cd | ||
|
|
3c593f5fdc | ||
|
|
7d23bca7f1 | ||
|
|
956ae53b59 | ||
|
|
7a1803df3a | ||
|
|
6e961a708c | ||
|
|
dab28a044a | ||
|
|
e2c7e54c16 | ||
|
|
d2728b455b | ||
|
|
cdc38ce2a0 | ||
|
|
7570d240ab | ||
|
|
7143eb84d2 | ||
|
|
541a4bbd19 | ||
|
|
4d0e75d806 | ||
|
|
fadb1c6aa4 | ||
|
|
1c48c5c1e4 | ||
|
|
0c575021b3 | ||
|
|
1229b9d2c6 | ||
|
|
515d32d491 | ||
|
|
a42a58f5b9 | ||
|
|
3794d1813f | ||
|
|
4d1fbf0255 | ||
|
|
23125bea5e | ||
|
|
f984c69100 | ||
|
|
3691890b95 | ||
|
|
1ce3920c03 | ||
|
|
29d6987275 | ||
|
|
7ad859704d | ||
|
|
51e139a964 | ||
|
|
6a88b536bf | ||
|
|
1be271715c | ||
|
|
7bee2d4540 | ||
|
|
542fdfd9a6 | ||
|
|
84122683c4 | ||
|
|
fd5067e66e | ||
|
|
ee42318b98 | ||
|
|
f2a2b4e65d | ||
|
|
c674e23d93 | ||
|
|
eb46a898cc | ||
|
|
2fe24d4781 | ||
|
|
3347adadaf | ||
|
|
af716ae755 | ||
|
|
0109d57bd9 | ||
|
|
c5b8ad7270 | ||
|
|
7e7bcd61e9 | ||
|
|
58a574b627 | ||
|
|
1f5e574ca3 | ||
|
|
ed7294cadb | ||
|
|
240342d52f | ||
|
|
f1c3de2836 | ||
|
|
64607f3680 | ||
|
|
f34e49ed71 | ||
|
|
b342a5df75 | ||
|
|
dcbd275c18 | ||
|
|
4cb840558f | ||
|
|
07e9a962d2 | ||
|
|
0b72fa0265 | ||
|
|
c7052c9aec | ||
|
|
5578df8e19 | ||
|
|
a4268afb7f | ||
|
|
5fb1cb63a2 | ||
|
|
135f4a93a5 | ||
|
|
71259001d3 | ||
|
|
55e2f72b27 | ||
|
|
ff5575b579 | ||
|
|
cbfbfeef9e | ||
|
|
ef33194e16 | ||
|
|
f0e90350b9 | ||
|
|
3870dc8172 | ||
|
|
23006311d2 | ||
|
|
446936496f | ||
|
|
b4f0067b4a | ||
|
|
884fbdf96d | ||
|
|
10e84868e7 | ||
|
|
ca04ee1ab1 | ||
|
|
f0c82cf325 | ||
|
|
2de8d089b8 |
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,7 +1,6 @@
|
||||
.DS_Store
|
||||
serra
|
||||
!src/serra
|
||||
_db/*
|
||||
/serra
|
||||
!_db/.placeholder
|
||||
_backup/*
|
||||
!_backup/.placeholder
|
||||
|
||||
51
.goreleaser.yaml
Normal file
51
.goreleaser.yaml
Normal file
@ -0,0 +1,51 @@
|
||||
version: 2
|
||||
before:
|
||||
hooks:
|
||||
- go mod tidy
|
||||
- go generate ./...
|
||||
builds:
|
||||
- env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- linux
|
||||
- windows
|
||||
- darwin
|
||||
- openbsd
|
||||
ldflags:
|
||||
- -X github.com/noqqe/serra/pkg/serra.Version={{.Tag}}
|
||||
flags:
|
||||
- -v
|
||||
main: ./cmd/serra
|
||||
archives:
|
||||
- id: serra
|
||||
name_template: >-
|
||||
{{ .ProjectName }}_
|
||||
{{- title .Os }}_
|
||||
{{- if eq .Arch "amd64" }}x86_64
|
||||
{{- else if eq .Arch "386" }}i386
|
||||
{{- else }}{{ .Arch }}{{ end }}
|
||||
changelog:
|
||||
sort: asc
|
||||
filters:
|
||||
exclude:
|
||||
- '^docs:'
|
||||
- '^test:'
|
||||
brews:
|
||||
- name: serra
|
||||
goarm: 6
|
||||
repository:
|
||||
owner: noqqe
|
||||
name: homebrew-tap
|
||||
download_strategy: CurlDownloadStrategy
|
||||
directory: Formula
|
||||
commit_author:
|
||||
name: goreleaserbot
|
||||
email: goreleaser@carlosbecker.com
|
||||
homepage: "https://github.com/noqqe/serra"
|
||||
description: "serra - Personal Magic: The Gathering Collection Tracker "
|
||||
license: "MIT"
|
||||
test: |
|
||||
system "#{bin}/serra --version"
|
||||
install: |
|
||||
bin.install "serra"
|
||||
|
||||
@ -1,97 +0,0 @@
|
||||
# This is an example .goreleaser.yml file with some sane defaults.
|
||||
# Make sure to check the documentation at http://goreleaser.com
|
||||
before:
|
||||
hooks:
|
||||
# You may remove this if you don't use go modules.
|
||||
- go mod tidy
|
||||
# you may remove this if you don't need go generate
|
||||
- go generate ./...
|
||||
builds:
|
||||
- env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- linux
|
||||
- windows
|
||||
- darwin
|
||||
- openbsd
|
||||
ldflags:
|
||||
- -X github.com/noqqe/serra/src/serra.Version={{.Tag}}
|
||||
flags:
|
||||
- -v
|
||||
archives:
|
||||
- id: serra
|
||||
name_template: >-
|
||||
{{ .ProjectName }}_
|
||||
{{- title .Os }}_
|
||||
{{- if eq .Arch "amd64" }}x86_64
|
||||
{{- else if eq .Arch "386" }}i386
|
||||
{{- else }}{{ .Arch }}{{ end }}
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
snapshot:
|
||||
name_template: "{{ .Tag }}-next"
|
||||
changelog:
|
||||
sort: asc
|
||||
filters:
|
||||
exclude:
|
||||
- '^docs:'
|
||||
- '^test:'
|
||||
brews:
|
||||
-
|
||||
# Name template of the recipe
|
||||
# Default to project name
|
||||
name: serra
|
||||
|
||||
# GOARM to specify which 32-bit arm version to use if there are multiple versions
|
||||
# from the build section. Brew formulas support atm only one 32-bit version.
|
||||
# Default is 6 for all artifacts or each id if there a multiple versions.
|
||||
goarm: 6
|
||||
|
||||
# NOTE: make sure the url_template, the token and given repo (github or gitlab) owner and name are from the
|
||||
# same kind. We will probably unify this in the next major version like it is done with scoop.
|
||||
|
||||
# GitHub/GitLab repository to push the formula to
|
||||
# Gitea is not supported yet, but the support coming
|
||||
tap:
|
||||
owner: noqqe
|
||||
name: homebrew-tap
|
||||
# Optionally a token can be provided, if it differs from the token provided to GoReleaser
|
||||
|
||||
# Allows you to set a custom download strategy. Note that you'll need
|
||||
# to implement the strategy and add it to your tap repository.
|
||||
# Example: https://docs.brew.sh/Formula-Cookbook#specifying-the-download-strategy-explicitly
|
||||
# Default is empty.
|
||||
download_strategy: CurlDownloadStrategy
|
||||
|
||||
# Git author used to commit to the repository.
|
||||
# Defaults are shown.
|
||||
commit_author:
|
||||
name: goreleaserbot
|
||||
email: goreleaser@carlosbecker.com
|
||||
|
||||
# Folder inside the repository to put the formula.
|
||||
# Default is the root folder.
|
||||
folder: Formula
|
||||
|
||||
# Your app's homepage.
|
||||
# Default is empty.
|
||||
homepage: "https://github.com/noqqe/serra"
|
||||
|
||||
# Your app's description.
|
||||
# Default is empty.
|
||||
description: "serra - Personal Magic: The Gathering Collection Tracker "
|
||||
|
||||
# SPDX identifier of your app's license.
|
||||
# Default is empty.
|
||||
license: "MIT"
|
||||
|
||||
# So you can `brew test` your formula.
|
||||
# Default is empty.
|
||||
test: |
|
||||
system "#{bin}/serra --version"
|
||||
|
||||
# Custom install script for brew.
|
||||
# Default is 'bin.install "program"'.
|
||||
install: |
|
||||
bin.install "serra"
|
||||
|
||||
@ -1 +1 @@
|
||||
golang 1.20
|
||||
golang 1.21.3
|
||||
|
||||
15
Dockerfile
15
Dockerfile
@ -1,23 +1,24 @@
|
||||
FROM golang: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
|
||||
COPY src /go/src/app/src
|
||||
COPY pkg /go/src/app/pkg
|
||||
COPY cmd /go/src/app/cmd
|
||||
COPY templates /go/src/app/templates
|
||||
COPY go.mod /go/src/app/go.mod
|
||||
COPY go.sum /go/src/app/go.sum
|
||||
COPY .git /go/src/app/.git
|
||||
COPY serra.go /go/src/app/serra.go
|
||||
|
||||
# build
|
||||
RUN go get -v ./...
|
||||
RUN go build -ldflags "-X github.com/noqqe/serra/src/serra.Version=`git describe --tags`" -v serra.go
|
||||
RUN go build -ldflags "-X github.com/noqqe/serra/pkg/serra.Version=`git describe --tags`" -v cmd/serra/serra.go
|
||||
|
||||
# copy
|
||||
FROM scratch
|
||||
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
|
||||
|
||||
# run
|
||||
|
||||
@ -5,17 +5,17 @@ version: '3'
|
||||
tasks:
|
||||
build:
|
||||
cmds:
|
||||
- go build -ldflags "-X github.com/noqqe/serra/src/serra.Version=`git describe --tags`" -v serra.go
|
||||
- go build -ldflags "-X github.com/noqqe/serra/pkg/serra.Version=`git describe --tags`" -v cmd/serra/serra.go
|
||||
sources:
|
||||
- "src/serra/**/*.go"
|
||||
- "serra.go"
|
||||
- "pkg/serra/**/*.go"
|
||||
- "cmd/serra/serra.go"
|
||||
generates:
|
||||
- "./serra"
|
||||
|
||||
release:
|
||||
interactive: true
|
||||
cmds:
|
||||
- git tag | tail -5
|
||||
- git tag | sort -t. -k 1,1n -k 2,2n -k 3,3n | tail -5
|
||||
- read -p "Version v1.1.1 " version ; git tag $version
|
||||
- git push --tags
|
||||
- goreleaser release --clean
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
// Package main provides a typing test
|
||||
package main
|
||||
|
||||
import "github.com/noqqe/serra/src/serra"
|
||||
import "github.com/noqqe/serra/pkg/serra"
|
||||
|
||||
func main() {
|
||||
serra.Execute()
|
||||
@ -2,7 +2,7 @@ version: '3.6'
|
||||
|
||||
services:
|
||||
mongo:
|
||||
image: mongo
|
||||
image: mongo:6
|
||||
restart: always
|
||||
ports:
|
||||
- 27017:27017
|
||||
|
||||
80
go.mod
80
go.mod
@ -1,27 +1,67 @@
|
||||
module github.com/noqqe/serra
|
||||
|
||||
go 1.14
|
||||
go 1.25.0
|
||||
|
||||
toolchain go1.25.4
|
||||
|
||||
require (
|
||||
github.com/charmbracelet/log v0.4.0
|
||||
github.com/chzyer/readline v1.5.1
|
||||
github.com/gin-gonic/gin v1.9.1
|
||||
github.com/go-playground/validator/v10 v10.14.1 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/go-cmp v0.5.6 // indirect
|
||||
github.com/klauspost/compress v1.16.6 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
||||
github.com/kr/pretty v0.3.0 // indirect
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||
github.com/rivo/uniseg v0.4.4 // indirect
|
||||
github.com/rogpeppe/go-internal v1.8.0 // indirect
|
||||
github.com/schollz/progressbar/v3 v3.13.1
|
||||
github.com/spf13/cobra v1.7.0
|
||||
github.com/xdg-go/scram v1.1.2 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect
|
||||
go.mongodb.org/mongo-driver v1.11.7
|
||||
golang.org/x/crypto v0.10.0 // indirect
|
||||
golang.org/x/sync v0.2.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
github.com/schollz/progressbar/v3 v3.16.1
|
||||
github.com/spf13/cobra v1.8.1
|
||||
go.mongodb.org/mongo-driver v1.17.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/bytedance/sonic v1.12.3 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.1 // indirect
|
||||
github.com/charmbracelet/lipgloss v0.13.0 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.3.2 // indirect
|
||||
github.com/cloudwego/base64x v0.1.4 // 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/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.22.1 // indirect
|
||||
github.com/goccy/go-json v0.10.3 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.17.11 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/montanaflynn/stats v0.7.1 // 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/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||
github.com/xdg-go/scram v1.1.2 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||
golang.org/x/arch v0.11.0 // indirect
|
||||
golang.org/x/crypto v0.28.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect
|
||||
golang.org/x/net v0.30.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/term v0.29.0 // indirect
|
||||
golang.org/x/text v0.19.0 // indirect
|
||||
google.golang.org/protobuf v1.35.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
216
go.sum
216
go.sum
@ -1,73 +1,75 @@
|
||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
|
||||
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/bytedance/sonic v1.12.3 h1:W2MGa7RCU1QTeYRTPE3+88mVC0yXmsRQRChiyVocVjU=
|
||||
github.com/bytedance/sonic v1.12.3/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.2.1 h1:1GgorWTqf12TA8mma4DDSbaQigE2wOgQo7iCjjJv3+E=
|
||||
github.com/bytedance/sonic/loader v0.2.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/charmbracelet/lipgloss v0.13.0 h1:4X3PPeoWEDCMvzDvGmTajSyYPcZM4+y8sCA/SsA3cjw=
|
||||
github.com/charmbracelet/lipgloss v0.13.0/go.mod h1:nw4zy0SBX/F/eAO1cWdcvy6qnkDUxr8Lw7dvFrAIbbY=
|
||||
github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM=
|
||||
github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM=
|
||||
github.com/charmbracelet/x/ansi v0.3.2 h1:wsEwgAN+C9U06l9dCVMX0/L3x7ptvY1qmjMwyfE6USY=
|
||||
github.com/charmbracelet/x/ansi v0.3.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
|
||||
github.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM=
|
||||
github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0iwWCOK1q10rlY=
|
||||
github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=
|
||||
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
|
||||
github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI=
|
||||
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
|
||||
github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
|
||||
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||
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/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
|
||||
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||
github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k=
|
||||
github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
|
||||
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
|
||||
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
|
||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.16.6 h1:91SKEy4K37vkp255cJ8QesJhjyRO0hn9i9G0GoUwLsk=
|
||||
github.com/klauspost/compress v1.16.6/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
@ -77,138 +79,102 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
|
||||
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
||||
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
|
||||
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
|
||||
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/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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
|
||||
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/schollz/progressbar/v3 v3.13.1 h1:o8rySDYiQ59Mwzy2FELeHY5ZARXZTVJC7iHD6PEFUiE=
|
||||
github.com/schollz/progressbar/v3 v3.13.1/go.mod h1:xvrbki8kfT1fzWzBT/UZd9L6GA+jdL7HAgq2RFnO6fQ=
|
||||
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||
github.com/schollz/progressbar/v3 v3.16.1 h1:RnF1neWZFzLCoGx8yp1yF7SDl4AzNDI5y4I0aUJRrZQ=
|
||||
github.com/schollz/progressbar/v3 v3.16.1/go.mod h1:I2ILR76gz5VXqYMIY/LdLecvMHDPVcQm3W/MSKi1TME=
|
||||
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
|
||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
|
||||
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
|
||||
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
|
||||
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
|
||||
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
|
||||
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
|
||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk=
|
||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4=
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.mongodb.org/mongo-driver v1.11.7 h1:LIwYxASDLGUg/8wOhgOOZhX8tQa/9tgZPgzZoVqJvcs=
|
||||
go.mongodb.org/mongo-driver v1.11.7/go.mod h1:G9TgswdsWjX4tmDA5zfs2+6AEPpYJwqblyjsfuh8oXY=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
||||
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
go.mongodb.org/mongo-driver v1.17.1 h1:Wic5cJIwJgSpBhe3lx3+/RybR5PiYRMpVFgO7cOHyIM=
|
||||
go.mongodb.org/mongo-driver v1.17.1/go.mod h1:wwWm/+BuOddhcq3n68LKRmgk2wXzmF6s0SFOa0GINL4=
|
||||
golang.org/x/arch v0.11.0 h1:KXV8WWKCXm6tRpLirl2szsO5j/oOODwZf4hATmGVNs4=
|
||||
golang.org/x/arch v0.11.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
||||
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
|
||||
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
|
||||
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
||||
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
|
||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
|
||||
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
|
||||
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
|
||||
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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.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-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28=
|
||||
golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
|
||||
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.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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/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.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
|
||||
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
|
||||
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
|
||||
@ -2,7 +2,6 @@ package serra
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@ -37,9 +36,9 @@ var addCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
func addCardsInteractive(unique bool, set string) {
|
||||
l := Logger()
|
||||
if len(set) == 0 {
|
||||
LogMessage("Error: --set must be given in interactive mode", "red")
|
||||
os.Exit(1)
|
||||
l.Fatal("Option --set <set> must be given in interactive mode")
|
||||
}
|
||||
|
||||
rl, err := readline.New(fmt.Sprintf("%s> ", set))
|
||||
@ -54,6 +53,12 @@ func addCardsInteractive(unique bool, set string) {
|
||||
break
|
||||
}
|
||||
|
||||
// default is no foil
|
||||
foil = false
|
||||
|
||||
// default is count 1
|
||||
count = 1
|
||||
|
||||
// construct card input for addCards
|
||||
card := []string{}
|
||||
|
||||
@ -73,7 +78,23 @@ func addCardsInteractive(unique bool, set string) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
card = append(card, fmt.Sprintf("%s/%s", set, strings.TrimSpace(line)))
|
||||
card = append(card, fmt.Sprintf("%s/%s", set, strings.Split(line, " ")[0]))
|
||||
}
|
||||
|
||||
// Are there extra arguments?
|
||||
if len(strings.Split(line, " ")) == 2 {
|
||||
|
||||
// foil shortcut
|
||||
if strings.Split(line, " ")[1] == "f" {
|
||||
foil = true
|
||||
}
|
||||
|
||||
// amount shortcut
|
||||
if amount, err := strconv.Atoi(strings.Split(line, " ")[1]); err == nil {
|
||||
if amount > 1 {
|
||||
count = int64(amount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addCards(card, unique, count)
|
||||
@ -84,46 +105,53 @@ func addCardsInteractive(unique bool, set string) {
|
||||
func addCards(cards []string, unique bool, 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")
|
||||
setName := strings.Split(card, "/")[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 {
|
||||
LogMessage(fmt.Sprintf("%v", err), "red")
|
||||
l.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
if len(co) >= 1 {
|
||||
c := co[0]
|
||||
outputColor := coloredValue(c.getValue(foil))
|
||||
|
||||
if unique {
|
||||
LogMessage(fmt.Sprintf("Not adding \"%s\" (%s, %.2f%s) to Collection because it already exists.", c.Name, c.Rarity, c.getValue(foil), getCurrency()), "red")
|
||||
l.Warnf("%dx \"%s\" (%s, %s%.2f%s%s) not added, because it already exists", count, c.Name, c.Rarity, outputColor, c.getValue(foil), getCurrency(), Reset)
|
||||
continue
|
||||
}
|
||||
|
||||
modifyCardCount(coll, &c, count, foil)
|
||||
|
||||
var total int64 = 0
|
||||
if foil {
|
||||
total = c.SerraCountFoil + count
|
||||
} else {
|
||||
total = c.SerraCount + count
|
||||
if askConfirmation(&c) {
|
||||
modifyCardCount(coll, &c, count, foil)
|
||||
}
|
||||
// Give feedback of successfully added card
|
||||
LogMessage(fmt.Sprintf("%dx \"%s\" (%s, %.2f%s) added to Collection.", total, c.Name, c.Rarity, c.getValue(foil), getCurrency()), "green")
|
||||
|
||||
// If card is not already in collection, fetching from scyfall
|
||||
} else {
|
||||
// Fetch card from scryfall
|
||||
c, err := fetchCard(setName, collectorNumber)
|
||||
|
||||
outputColor := coloredValue(c.getValue(foil))
|
||||
if err != nil {
|
||||
LogMessage(fmt.Sprintf("%v", err), "red")
|
||||
l.Warn(err)
|
||||
continue
|
||||
}
|
||||
|
||||
@ -131,19 +159,30 @@ func addCards(cards []string, unique bool, count int64) error {
|
||||
var total int64 = 0
|
||||
if foil {
|
||||
c.SerraCountFoil = count
|
||||
c.SerraCountFoilDeck = 0
|
||||
total = c.SerraCountFoil
|
||||
} else {
|
||||
c.SerraCount = count
|
||||
c.SerraCountDeck = 0
|
||||
total = c.SerraCount
|
||||
}
|
||||
|
||||
if !askConfirmation(c) {
|
||||
continue
|
||||
}
|
||||
|
||||
err = coll.storageAdd(c)
|
||||
if err != nil {
|
||||
LogMessage(fmt.Sprintf("%v", err), "red")
|
||||
l.Warn(err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Give feedback of successfully added card
|
||||
LogMessage(fmt.Sprintf("%dx \"%s\" (%s, %.2f%s) added to Collection.", total, c.Name, c.Rarity, c.getValue(foil), getCurrency()), "green")
|
||||
if foil {
|
||||
l.Infof("%dx \"%s\" (%s, %s%.2f%s%s, foil) added", total, c.Name, c.Rarity, outputColor, c.getValue(foil), getCurrency(), Reset)
|
||||
} else {
|
||||
l.Infof("%dx \"%s\" (%s, %s%.2f%s%s) added", total, c.Name, c.Rarity, outputColor, c.getValue(foil), getCurrency(), Reset)
|
||||
}
|
||||
}
|
||||
}
|
||||
storageDisconnect(client)
|
||||
@ -1,25 +1,36 @@
|
||||
package serra
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"image/png"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/dolmen-go/kittyimg"
|
||||
"github.com/nfnt/resize"
|
||||
"github.com/spf13/cobra"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
)
|
||||
|
||||
func init() {
|
||||
cardCmd.Flags().StringVarP(&artist, "artist", "a", "", "Filter by name of artist")
|
||||
cardCmd.Flags().StringVarP(&rarity, "rarity", "r", "", "Filter by rarity of cards (mythic, rare, uncommon, common)")
|
||||
cardCmd.Flags().StringVarP(&set, "set", "e", "", "Filter by set code (usg/mmq/vow)")
|
||||
cardCmd.Flags().StringVarP(&sortby, "sort", "s", "name", "How to sort cards (value/number/name/added)")
|
||||
cardCmd.Flags().StringVarP(&name, "name", "n", "", "Name of the card (regex compatible)")
|
||||
cardCmd.Flags().Int64VarP(&cmc, "cmc", "m", -1, "Cumulative mana cost of card")
|
||||
cardCmd.Flags().StringVarP(&color, "color", "i", "", "Color identity of card (w,u,b,r,g)")
|
||||
cardCmd.Flags().StringVarP(&oracle, "oracle", "o", "", "Contains string in card text")
|
||||
cardCmd.Flags().StringVarP(&cardType, "type", "t", "", "Contains string in card type line")
|
||||
cardCmd.Flags().Int64VarP(&count, "min-count", "c", 0, "Occource more than X in your collection")
|
||||
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(&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)
|
||||
}
|
||||
|
||||
@ -33,7 +44,7 @@ otherwise you'll get a list of cards as a search result.`,
|
||||
SilenceErrors: true,
|
||||
RunE: func(cmd *cobra.Command, cards []string) error {
|
||||
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)
|
||||
} else {
|
||||
ShowCard(cards)
|
||||
@ -45,11 +56,12 @@ otherwise you'll get a list of cards as a search result.`,
|
||||
func ShowCard(cardids []string) {
|
||||
client := storageConnect()
|
||||
coll := &Collection{client.Database("serra").Collection("cards")}
|
||||
l := Logger()
|
||||
defer storageDisconnect(client)
|
||||
|
||||
for _, v := range cardids {
|
||||
if len(strings.Split(v, "/")) < 2 || strings.Split(v, "/")[1] == "" {
|
||||
LogMessage(fmt.Sprintf("Invalid card %s", v), "red")
|
||||
l.Warnf("Invalid card %s", v)
|
||||
continue
|
||||
}
|
||||
|
||||
@ -61,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()
|
||||
coll := &Collection{client.Database("serra").Collection("cards")}
|
||||
defer storageDisconnect(client)
|
||||
@ -75,12 +87,14 @@ func Cards(rarity, set, sortby, name, oracle, cardType string, reserved, foil bo
|
||||
filter = append(filter, bson.E{"rarity", "common"})
|
||||
case "rare":
|
||||
filter = append(filter, bson.E{"rarity", "rare"})
|
||||
case "mythic":
|
||||
filter = append(filter, bson.E{"rarity", "mythic"})
|
||||
}
|
||||
|
||||
var sortStage bson.D
|
||||
switch sortby {
|
||||
case "value":
|
||||
if getCurrency() == "EUR" {
|
||||
if getCurrency() == EUR {
|
||||
sortStage = bson.D{{"prices.eur", 1}}
|
||||
} else {
|
||||
sortStage = bson.D{{"prices.usd", 1}}
|
||||
@ -103,6 +117,14 @@ func Cards(rarity, set, sortby, name, oracle, cardType string, reserved, foil bo
|
||||
filter = append(filter, bson.E{"name", bson.D{{"$regex", ".*" + name + ".*"}, {"$options", "i"}}})
|
||||
}
|
||||
|
||||
if len(artist) > 0 {
|
||||
filter = append(filter, bson.E{"artist", bson.D{{"$regex", ".*" + artist + ".*"}, {"$options", "i"}}})
|
||||
}
|
||||
|
||||
if cmc > -1 {
|
||||
filter = append(filter, bson.E{"cmc", cmc})
|
||||
}
|
||||
|
||||
if len(oracle) > 0 {
|
||||
filter = append(filter, bson.E{"oracletext", bson.D{{"$regex", ".*" + oracle + ".*"}, {"$options", "i"}}})
|
||||
}
|
||||
@ -111,6 +133,11 @@ func Cards(rarity, set, sortby, name, oracle, cardType string, reserved, foil bo
|
||||
filter = append(filter, bson.E{"typeline", bson.D{{"$regex", ".*" + cardType + ".*"}, {"$options", "i"}}})
|
||||
}
|
||||
|
||||
if len(color) > 0 {
|
||||
colorArr := strings.Split(strings.ToUpper(color), ",")
|
||||
filter = append(filter, bson.E{"coloridentity", colorArr})
|
||||
}
|
||||
|
||||
if reserved {
|
||||
filter = append(filter, bson.E{"reserved", true})
|
||||
}
|
||||
@ -119,8 +146,11 @@ func Cards(rarity, set, sortby, name, oracle, cardType string, reserved, foil bo
|
||||
filter = append(filter, bson.E{"serra_count_foil", bson.D{{"$gt", 0}}})
|
||||
}
|
||||
|
||||
if count > 0 {
|
||||
filter = append(filter, bson.E{"serra_count", bson.D{{"$gte", count}}})
|
||||
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)
|
||||
@ -133,42 +163,81 @@ func Cards(rarity, set, sortby, name, oracle, cardType string, reserved, foil bo
|
||||
})
|
||||
}
|
||||
|
||||
// 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
|
||||
// aggregating fields (of count and countFoil).
|
||||
temp := cards[:0]
|
||||
for _, card := range cards {
|
||||
if (card.SerraCount + card.SerraCountFoil) >= count {
|
||||
temp = append(temp, card)
|
||||
}
|
||||
}
|
||||
cards = temp
|
||||
|
||||
return cards
|
||||
}
|
||||
|
||||
func showCardList(cards []Card, detail bool) {
|
||||
|
||||
var total float64
|
||||
if detail {
|
||||
if drawImg {
|
||||
for _, card := range cards {
|
||||
drawImage(&card)
|
||||
}
|
||||
} else if detail {
|
||||
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)
|
||||
total = total + card.getValue(false)*float64(card.SerraCount) + card.getValue(true)*float64(card.SerraCountFoil)
|
||||
}
|
||||
} else {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
fmt.Printf("%s%s%s (%s/%s)\n", Purple, card.Name, Reset, card.Set, card.CollectorNumber)
|
||||
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))
|
||||
if drawImg {
|
||||
drawImage(card)
|
||||
} else {
|
||||
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("* Normal: %dx %s%.2f%s%s\n", card.SerraCount, Yellow, card.getValue(false), getCurrency(), Reset)
|
||||
if card.SerraCountFoil > 0 {
|
||||
fmt.Printf("* Foil: %dx %s%.2f%s%s\n", card.SerraCountFoil, Yellow, card.getValue(true), getCurrency(), 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)
|
||||
if card.SerraCountFoil > 0 {
|
||||
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
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
@ -30,34 +30,45 @@ func checkCards(cards []string, detail bool) error {
|
||||
client := storageConnect()
|
||||
coll := &Collection{client.Database("serra").Collection("cards")}
|
||||
defer storageDisconnect(client)
|
||||
l := Logger()
|
||||
|
||||
// Loop over different cards
|
||||
for _, card := range cards {
|
||||
|
||||
if !strings.Contains(card, "/") {
|
||||
l.Errorf("Invalid card format %s. Needs to be set/collector number i.e. \"usg/13\"", card)
|
||||
continue
|
||||
}
|
||||
|
||||
// Extract collector number and set name from card input & trim any leading 0 from collector number
|
||||
collectorNumber := strings.TrimLeft(strings.Split(card, "/")[1], "0")
|
||||
setName := strings.Split(card, "/")[0]
|
||||
setName := strings.ToLower(strings.Split(card, "/")[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 {
|
||||
LogMessage(fmt.Sprintf("%v", err), "red")
|
||||
l.Warn(err)
|
||||
continue
|
||||
}
|
||||
|
||||
// If Card is in collection, print yes.
|
||||
if len(co) >= 1 {
|
||||
c := co[0]
|
||||
LogMessage(fmt.Sprintf("Yes - %s \"%s\" (%s, %.2f%s) is in your Collection", card, c.Name, c.Rarity, c.getValue(foil), getCurrency()), "green")
|
||||
fmt.Printf("PRESENT %s \"%s\" (%s, %.2f%s) %s\n", card, c.Name, c.Rarity, c.getValue(foil), getCurrency(), strings.Replace(c.ScryfallURI, "?utm_source=api", "", 1))
|
||||
continue
|
||||
} else {
|
||||
if detail {
|
||||
// fetch card from scyrfall if --detail was given
|
||||
c, _ := fetchCard(setName, collectorNumber)
|
||||
LogMessage(fmt.Sprintf("No - %s \"%s\" (%s, %.2f%s) is not in your Collection.", card, c.Name, c.Rarity, c.getValue(foil), getCurrency()), "red")
|
||||
fmt.Printf("MISSING %s \"%s\" (%s, %.2f%s) %s\n", card, c.Name, c.Rarity, c.getValue(foil), getCurrency(), strings.Replace(c.ScryfallURI, "?utm_source=api", "", 1))
|
||||
} else {
|
||||
// Just print, the card name was not found
|
||||
LogMessage(fmt.Sprintf("No \"%s\" is not in your Collection.", card), "red")
|
||||
fmt.Printf("MISSING \"%s\"\n", card)
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
@ -1,14 +1,17 @@
|
||||
package serra
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
const EUR = "€"
|
||||
const USD = "$"
|
||||
|
||||
func getMongoDBURI() string {
|
||||
l := Logger()
|
||||
uri := os.Getenv("MONGODB_URI")
|
||||
if uri == "" {
|
||||
log.Fatal("You must set your 'MONGODB_URI' environmental variable. See\n\t https://docs.mongodb.com/drivers/go/current/usage-examples/#environment-variable")
|
||||
l.Fatal("You must set 'MONGODB_URI' environmental variable. See\n\t https://docs.mongodb.com/drivers/go/current/usage-examples/#environment-variable")
|
||||
}
|
||||
|
||||
return uri
|
||||
@ -17,15 +20,14 @@ func getMongoDBURI() string {
|
||||
// Returns configured human readable name for
|
||||
// the configured currency of the user
|
||||
func getCurrency() string {
|
||||
l := Logger()
|
||||
switch os.Getenv("SERRA_CURRENCY") {
|
||||
case "EUR":
|
||||
return "€"
|
||||
return EUR
|
||||
case "USD":
|
||||
return USD
|
||||
default:
|
||||
l.Warn("You did not configure SERRA_CURRENCY. Assuming \"USD\"")
|
||||
return "$"
|
||||
}
|
||||
|
||||
// default
|
||||
LogMessage("Warning: You did not configure SERRA_CURRENCY. Assuming \"USD\"", "yellow")
|
||||
|
||||
return "USD"
|
||||
}
|
||||
127
pkg/serra/export.go
Normal file
127
pkg/serra/export.go
Normal file
@ -0,0 +1,127 @@
|
||||
package serra
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
exportCmd.Flags().StringVarP(&set, "set", "e", "", "Filter by set code (usg/mmq/vow)")
|
||||
exportCmd.Flags().StringVarP(&format, "format", "f", "tcgpowertools", "Choose format to export (tcgpowertools/json)")
|
||||
exportCmd.Flags().Int64VarP(&count, "min-count", "c", 0, "Occource more than X in your collection")
|
||||
rootCmd.AddCommand(exportCmd)
|
||||
}
|
||||
|
||||
var exportCmd = &cobra.Command{
|
||||
Use: "export",
|
||||
Short: "Export cards from your collection",
|
||||
Long: `Export cards from your collection.
|
||||
Your data. Your choice.
|
||||
Supports multiple output formats depending on where you want to export your collection.`,
|
||||
SilenceErrors: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
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)
|
||||
// this is done after query result because find query constructed does not support
|
||||
// aggregating fields (of count and countFoil).
|
||||
temp := cardList[:0]
|
||||
for _, card := range cardList {
|
||||
if (card.SerraCount + card.SerraCountFoil) >= count {
|
||||
temp = append(temp, card)
|
||||
}
|
||||
}
|
||||
cardList = temp
|
||||
|
||||
switch format {
|
||||
case "tcgpowertools":
|
||||
exportTCGPowertools(cardList)
|
||||
case "tcghome":
|
||||
exportTCGHome(cardList)
|
||||
case "moxfield":
|
||||
exportMoxfield(cardList)
|
||||
case "json":
|
||||
exportJSON(cardList)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func exportTCGPowertools(cards []Card) {
|
||||
|
||||
// TCGPowertools.com Example
|
||||
// idProduct,quantity,name,set,condition,language,isFoil,isPlayset,isSigned,isFirstEd,price,comment
|
||||
// 260009,1,Totally Lost,Gatecrash,GD,English,true,true,,,1000,
|
||||
// 260009,1,Totally Lost,Gatecrash,NM,English,true,true,,,1000,
|
||||
|
||||
fmt.Println("quantity,cardmarketId,name,set,condition,language,isFoil,isPlayset,price,comment")
|
||||
for _, card := range cards {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
func exportMoxfield(cards []Card) {
|
||||
|
||||
// Structure
|
||||
// https://www.moxfield.com/help/importing-collection
|
||||
|
||||
records := [][]string{{
|
||||
"Count", "Name", "Edition", "Condition", "Language", "Foil", "Collector Number", "Alter", "Proxy", "Purchase Price"}}
|
||||
|
||||
w := csv.NewWriter(os.Stdout)
|
||||
|
||||
for _, card := range cards {
|
||||
records = append(records,
|
||||
[]string{fmt.Sprintf("%d", card.SerraCount+card.SerraCountFoil), card.Name, card.Set, "NM", "English", "FALSE", card.CollectorNumber, "FALSE", "FALSE", ""})
|
||||
}
|
||||
|
||||
for _, record := range records {
|
||||
if err := w.Write(record); err != nil {
|
||||
log.Fatalln("error writing record to csv:", err)
|
||||
}
|
||||
}
|
||||
|
||||
w.Flush()
|
||||
|
||||
if err := w.Error(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func exportTCGHome(cards []Card) {
|
||||
|
||||
// Strucutre
|
||||
// https://app.tcg-home.com/e686ea62-7078-4f52-bd6f-515e18c7dc6a
|
||||
|
||||
records := [][]string{{
|
||||
"amount", "name", "finish", "set", "collector_number", "language", "condition", "scryfall_id", "purchase_price"}}
|
||||
|
||||
w := csv.NewWriter(os.Stdout)
|
||||
|
||||
for _, card := range cards {
|
||||
records = append(records,
|
||||
[]string{fmt.Sprintf("%d", card.SerraCount+card.SerraCountFoil), card.Name, "", card.Set, card.CollectorNumber, "English", "EX", card.ID, ""})
|
||||
}
|
||||
|
||||
for _, record := range records {
|
||||
if err := w.Write(record); err != nil {
|
||||
log.Fatalln("error writing record to csv:", err)
|
||||
}
|
||||
}
|
||||
|
||||
w.Flush()
|
||||
|
||||
if err := w.Error(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func exportJSON(cards []Card) {
|
||||
ehj, _ := json.MarshalIndent(cards, "", " ")
|
||||
fmt.Println(string(ehj))
|
||||
}
|
||||
@ -57,7 +57,7 @@ func Gains(limit float64, sort int) error {
|
||||
}
|
||||
|
||||
currencyField := "$serra_prices.usd"
|
||||
if getCurrency() == "EUR" {
|
||||
if getCurrency() == EUR {
|
||||
currencyField = "$serra_prices.eur"
|
||||
}
|
||||
|
||||
@ -4,10 +4,12 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/charmbracelet/log"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
@ -16,16 +18,48 @@ type Rarities struct {
|
||||
Rares, Uncommons, Commons, Mythics float64
|
||||
}
|
||||
|
||||
func modifyCardCount(coll *Collection, c *Card, amount int64, foil bool) error {
|
||||
var (
|
||||
Icon = "\U0001F9D9\U0001F3FC"
|
||||
Reset = "\033[0m"
|
||||
Background = "\033[38;5;59m"
|
||||
CurrentLine = "\033[38;5;60m"
|
||||
Foreground = "\033[38;5;231m"
|
||||
Comment = "\033[38;5;103m"
|
||||
Cyan = "\033[38;5;159m"
|
||||
Green = "\033[38;5;120m"
|
||||
Orange = "\033[38;5;222m"
|
||||
Pink = "\033[38;5;212m"
|
||||
Purple = "\033[38;5;183m"
|
||||
Red = "\033[38;5;210m"
|
||||
Yellow = "\033[38;5;229m"
|
||||
)
|
||||
|
||||
func Logger() *log.Logger {
|
||||
|
||||
l := log.New(os.Stderr)
|
||||
l.SetReportTimestamp(false)
|
||||
return l
|
||||
}
|
||||
|
||||
func getStoredCard(coll *Collection, c *Card) (Card, error) {
|
||||
// find already existing card
|
||||
sort := bson.D{{"_id", 1}}
|
||||
searchFilter := bson.D{{"_id", c.ID}}
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
storedCard := storedCards[0]
|
||||
|
||||
// update card amount
|
||||
var update bson.M
|
||||
@ -44,10 +78,49 @@ func modifyCardCount(coll *Collection, c *Card, amount int64, foil bool) error {
|
||||
var total int64
|
||||
if foil {
|
||||
total = storedCard.SerraCountFoil + amount
|
||||
if amount < 0 {
|
||||
l.Warnf("Reduced card amount of \"%s\" (%.2f%s, foil) from %d to %d", storedCard.Name, storedCard.getValue(true), getCurrency(), storedCard.SerraCountFoil, total)
|
||||
} else {
|
||||
l.Warnf("Increased card amount of \"%s\" (%.2f%s, foil) from %d to %d", storedCard.Name, storedCard.getValue(true), getCurrency(), storedCard.SerraCountFoil, total)
|
||||
}
|
||||
} else {
|
||||
total = storedCard.SerraCount + amount
|
||||
if amount < 0 {
|
||||
l.Warnf("Reduced card amount of \"%s\" (%.2f%s) from %d to %d", storedCard.Name, storedCard.getValue(false), getCurrency(), storedCard.SerraCount, total)
|
||||
} else {
|
||||
l.Warnf("Increased card amount of \"%s\" (%.2f%s) from %d to %d", storedCard.Name, storedCard.getValue(false), getCurrency(), storedCard.SerraCount, total)
|
||||
}
|
||||
}
|
||||
LogMessage(fmt.Sprintf("Updating Card \"%s\" amount to %d", storedCard.Name, total), "purple")
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@ -106,7 +179,6 @@ func convertManaSymbols(sym []interface{}) string {
|
||||
var mana string
|
||||
|
||||
if len(sym) == 0 {
|
||||
// mana = mana + "\U0001F6AB" //probibited sign for lands
|
||||
mana = mana + "None" //probibited sign for lands
|
||||
}
|
||||
|
||||
@ -114,19 +186,14 @@ func convertManaSymbols(sym []interface{}) string {
|
||||
switch v {
|
||||
case "B":
|
||||
mana = mana + "Black" //black
|
||||
//mana = mana + "\U000026AB" //black
|
||||
case "R":
|
||||
mana = mana + "Red" //red
|
||||
// mana = mana + "\U0001F534" //red
|
||||
case "G":
|
||||
mana = mana + "Green" //green
|
||||
// mana = mana + "\U0001F7E2" //green
|
||||
case "U":
|
||||
mana = mana + "Blue" //blue
|
||||
//mana = mana + "\U0001F535" //blue
|
||||
case "W":
|
||||
mana = mana + "White" //white
|
||||
// mana = mana + "\U000026AA" //white
|
||||
}
|
||||
}
|
||||
return mana
|
||||
@ -167,14 +234,16 @@ func showPriceHistory(prices []PriceEntry, prefix string, total bool) {
|
||||
|
||||
var value float64
|
||||
if total {
|
||||
value = e.Usd + e.UsdFoil + e.UsdEtched
|
||||
if getCurrency() == "EUR" {
|
||||
if getCurrency() == EUR {
|
||||
value = e.Eur + e.EurFoil
|
||||
} else {
|
||||
value = e.Usd + e.UsdFoil
|
||||
}
|
||||
} else {
|
||||
value = e.Usd
|
||||
if getCurrency() == "EUR" {
|
||||
if getCurrency() == EUR {
|
||||
value = e.Eur
|
||||
} else {
|
||||
value = e.Usd
|
||||
}
|
||||
}
|
||||
|
||||
@ -219,6 +288,36 @@ func getFloat64(unknown interface{}) (float64, error) {
|
||||
case uint:
|
||||
return float64(i), nil
|
||||
default:
|
||||
return math.NaN(), errors.New("Non-numeric type could not be converted to float")
|
||||
return math.NaN(), errors.New("non-numeric type could not be converted to float")
|
||||
}
|
||||
}
|
||||
|
||||
func coloredValue(value float64) string {
|
||||
|
||||
outputColor := Reset
|
||||
|
||||
if value > 1 {
|
||||
outputColor = Green
|
||||
}
|
||||
if value > 5 {
|
||||
outputColor = Yellow
|
||||
}
|
||||
if value > 10 {
|
||||
outputColor = Red
|
||||
}
|
||||
|
||||
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'
|
||||
}
|
||||
@ -23,12 +23,13 @@ cards you dont own (yet) :)`,
|
||||
RunE: func(cmd *cobra.Command, setName []string) error {
|
||||
client := storageConnect()
|
||||
coll := &Collection{client.Database("serra").Collection("cards")}
|
||||
l := Logger()
|
||||
defer storageDisconnect(client)
|
||||
|
||||
// fetch all cards in set
|
||||
cards, err := coll.storageFind(bson.D{{"set", setName[0]}}, bson.D{{"collectornumber", 1}}, 0, 0)
|
||||
if (err != nil) || len(cards) == 0 {
|
||||
LogMessage(fmt.Sprintf("Error: Set %s not found or no card in your collection.", setName[0]), "red")
|
||||
l.Errorf("Set %s not found or no card in your collection.", setName[0])
|
||||
return err
|
||||
}
|
||||
|
||||
@ -37,7 +38,7 @@ cards you dont own (yet) :)`,
|
||||
sets, _ := setcoll.storageFindSet(bson.D{{"code", setName[0]}}, bson.D{{"_id", 1}})
|
||||
set := sets[0]
|
||||
|
||||
LogMessage(fmt.Sprintf("Missing cards in %s", sets[0].Name), "green")
|
||||
fmt.Printf("Missing cards in %s\n", sets[0].Name)
|
||||
|
||||
// generate set with all setnumbers
|
||||
var (
|
||||
@ -75,7 +76,7 @@ cards you dont own (yet) :)`,
|
||||
})
|
||||
|
||||
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
|
||||
@ -2,7 +2,6 @@ package serra
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/chzyer/readline"
|
||||
@ -27,7 +26,7 @@ var removeCmd = &cobra.Command{
|
||||
RunE: func(cmd *cobra.Command, cards []string) error {
|
||||
|
||||
if interactive {
|
||||
removeCardsInteractive(unique, set)
|
||||
removeCardsInteractive(set)
|
||||
} else {
|
||||
removeCards(cards, count)
|
||||
}
|
||||
@ -35,10 +34,11 @@ var removeCmd = &cobra.Command{
|
||||
},
|
||||
}
|
||||
|
||||
func removeCardsInteractive(unique bool, set string) {
|
||||
func removeCardsInteractive(set string) {
|
||||
l := Logger()
|
||||
|
||||
if len(set) == 0 {
|
||||
LogMessage("Error: --set must be given in interactive mode", "red")
|
||||
os.Exit(1)
|
||||
l.Fatal("Option --set must be given in interactive mode")
|
||||
}
|
||||
|
||||
rl, err := readline.New(fmt.Sprintf("%s> ", set))
|
||||
@ -66,34 +66,50 @@ func removeCards(cards []string, count int64) error {
|
||||
// Connect to the DB & load the collection
|
||||
client := storageConnect()
|
||||
coll := &Collection{client.Database("serra").Collection("cards")}
|
||||
l := Logger()
|
||||
defer storageDisconnect(client)
|
||||
|
||||
// Loop over different cards
|
||||
for _, card := range cards {
|
||||
|
||||
if !strings.Contains(card, "/") {
|
||||
l.Errorf("Invalid card format %s. Needs to be set/collector number i.e. \"usg/13\"", card)
|
||||
continue
|
||||
}
|
||||
|
||||
// Extract collector number and set name from input & remove leading zeros
|
||||
collectorNumber := strings.TrimLeft(strings.Split(card, "/")[1], "0")
|
||||
setName := strings.Split(card, "/")[0]
|
||||
|
||||
if collectorNumber == "" {
|
||||
l.Errorf("Invalid card format %s. Needs to be set/collector number i.e. \"usg/13\"", card)
|
||||
continue
|
||||
}
|
||||
|
||||
// Fetch card from scryfall
|
||||
c, err := findCardByCollectorNumber(coll, setName, collectorNumber)
|
||||
if err != nil {
|
||||
LogMessage(fmt.Sprintf("%v", err), "red")
|
||||
l.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
if foil && c.SerraCountFoil < 1 {
|
||||
LogMessage(fmt.Sprintf("Error: No Foil \"%s\" in the Collection.", c.Name), "red")
|
||||
l.Errorf("No foil \"%s\" in the collection", c.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
if !foil && c.SerraCount < 1 {
|
||||
LogMessage(fmt.Sprintf("Error: No normal \"%s\" in the Collection.", c.Name), "red")
|
||||
l.Errorf("No normal \"%s\" in the collection", c.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
if !askConfirmation(c) {
|
||||
continue
|
||||
}
|
||||
|
||||
if foil && c.SerraCountFoil == 1 && c.SerraCount == 0 || !foil && c.SerraCount == 1 && c.SerraCountFoil == 0 {
|
||||
coll.storageRemove(bson.M{"_id": c.ID})
|
||||
LogMessage(fmt.Sprintf("\"%s\" (%.2f%s) removed from the Collection.", c.Name, c.getValue(foil), getCurrency()), "green")
|
||||
l.Infof("\"%s\" (%.2f%s) removed", c.Name, c.getValue(foil), getCurrency())
|
||||
} else {
|
||||
modifyCardCount(coll, c, -count, foil)
|
||||
}
|
||||
@ -1,22 +1,29 @@
|
||||
// Package serra
|
||||
//
|
||||
// It implements base functions and also cli wrappers
|
||||
// The entire tool consists only of this one package.
|
||||
package serra
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
Version = "unknown"
|
||||
address string
|
||||
artist string
|
||||
cardType string
|
||||
color string
|
||||
cmc int64
|
||||
count int64
|
||||
detail bool
|
||||
drawImg bool
|
||||
foil bool
|
||||
format string
|
||||
interactive bool
|
||||
limit float64
|
||||
name string
|
||||
omitInDeck bool
|
||||
oracle string
|
||||
port uint64
|
||||
rarity string
|
||||
@ -37,8 +44,9 @@ var rootCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
func Execute() {
|
||||
|
||||
l := Logger()
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
l.Fatal(err)
|
||||
}
|
||||
}
|
||||
@ -2,11 +2,14 @@ package serra
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
@ -14,18 +17,23 @@ import (
|
||||
|
||||
type Card struct {
|
||||
// Added by Serra
|
||||
SerraCount int64 `bson:"serra_count"`
|
||||
SerraCountFoil int64 `bson:"serra_count_foil"`
|
||||
SerraCountEtched int64 `bson:"serra_count_etched"`
|
||||
SerraPrices []PriceEntry `bson:"serra_prices"`
|
||||
SerraCreated primitive.DateTime `bson:"serra_created"`
|
||||
SerraUpdated primitive.DateTime `bson:"serra_updated"`
|
||||
SerraCount int64 `bson:"serra_count"`
|
||||
SerraCountFoil int64 `bson:"serra_count_foil"`
|
||||
SerraCountEtched int64 `bson:"serra_count_etched"`
|
||||
SerraCountDeck int64 `bson:"serra_count_deck"`
|
||||
SerraCountFoilDeck int64 `bson:"serra_count_foil_deck"`
|
||||
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"`
|
||||
ArtistIds []string `json:"artist_ids"`
|
||||
Booster bool `json:"booster"`
|
||||
BorderColor string `json:"border_color"`
|
||||
CardBackID string `json:"card_back_id"`
|
||||
CardmarketID float64 `json:"cardmarket_id"`
|
||||
Cmc float64 `json:"cmc"`
|
||||
CollectorNumber string `json:"collector_number"`
|
||||
ColorIdentity []string `json:"color_identity"`
|
||||
@ -98,37 +106,139 @@ type Card struct {
|
||||
TcgplayerInfiniteArticles string `json:"tcgplayer_infinite_articles"`
|
||||
TcgplayerInfiniteDecks string `json:"tcgplayer_infinite_decks"`
|
||||
} `json:"related_uris"`
|
||||
ReleasedAt string `json:"released_at"`
|
||||
Reprint bool `json:"reprint"`
|
||||
Reserved bool `json:"reserved"`
|
||||
RulingsURI string `json:"rulings_uri"`
|
||||
ScryfallSetURI string `json:"scryfall_set_uri"`
|
||||
ScryfallURI string `json:"scryfall_uri"`
|
||||
Set string `json:"set"`
|
||||
SetID string `json:"set_id"`
|
||||
SetName string `json:"set_name"`
|
||||
SetSearchURI string `json:"set_search_uri"`
|
||||
SetType string `json:"set_type"`
|
||||
SetURI string `json:"set_uri"`
|
||||
StorySpotlight bool `json:"story_spotlight"`
|
||||
Textless bool `json:"textless"`
|
||||
TypeLine string `json:"type_line"`
|
||||
URI string `json:"uri"`
|
||||
Variation bool `json:"variation"`
|
||||
ReleasedAt string `json:"released_at"`
|
||||
Reprint bool `json:"reprint"`
|
||||
Reserved bool `json:"reserved"`
|
||||
RulingsURI string `json:"rulings_uri"`
|
||||
ScryfallSetURI string `json:"scryfall_set_uri"`
|
||||
ScryfallURI string `json:"scryfall_uri"`
|
||||
Set string `json:"set"`
|
||||
SetID string `json:"set_id"`
|
||||
SetName string `json:"set_name"`
|
||||
SetSearchURI string `json:"set_search_uri"`
|
||||
SetType string `json:"set_type"`
|
||||
SetURI string `json:"set_uri"`
|
||||
StorySpotlight bool `json:"story_spotlight"`
|
||||
Textless bool `json:"textless"`
|
||||
TCGPlayerID float64 `json:"tcgplayer_id"`
|
||||
TypeLine string `json:"type_line"`
|
||||
URI string `json:"uri"`
|
||||
Variation bool `json:"variation"`
|
||||
}
|
||||
|
||||
// Getter for currency specific value
|
||||
func (c Card) getValue(foil bool) float64 {
|
||||
if getCurrency() == "EUR" {
|
||||
if foil {
|
||||
return c.Prices.EurFoil
|
||||
type BulkIndex struct {
|
||||
Object string `json:"object"`
|
||||
HasMore bool `json:"has_more"`
|
||||
Data []struct {
|
||||
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 {
|
||||
@ -168,6 +278,20 @@ type Set struct {
|
||||
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) {
|
||||
resp, err := http.Get(fmt.Sprintf("https://api.scryfall.com/cards/%s/%s/", setName, collectorNumber))
|
||||
if err != nil {
|
||||
@ -179,8 +303,8 @@ func fetchCard(setName, collectorNumber string) (*Card, error) {
|
||||
return &Card{}, fmt.Errorf("Card %s/%s not found", setName, collectorNumber)
|
||||
}
|
||||
|
||||
//We Read the response body on the line below.
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
//we read the response body on the line below.
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Fatalf("%s", err)
|
||||
return &Card{}, err
|
||||
@ -189,7 +313,6 @@ func fetchCard(setName, collectorNumber string) (*Card, error) {
|
||||
r := bytes.NewReader(body)
|
||||
decoder := json.NewDecoder(r)
|
||||
val := &Card{}
|
||||
|
||||
err = decoder.Decode(val)
|
||||
if err != nil {
|
||||
log.Fatalf("%s", err)
|
||||
@ -202,11 +325,24 @@ func fetchCard(setName, collectorNumber string) (*Card, error) {
|
||||
val.Prices.Date = primitive.NewDateTimeFromTime(time.Now())
|
||||
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
|
||||
}
|
||||
|
||||
func fetchSets() (*SetList, error) {
|
||||
// TODO better URL Building...
|
||||
// TODO: better URL Building...
|
||||
resp, err := http.Get("https://api.scryfall.com/sets")
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
@ -218,7 +354,7 @@ func fetchSets() (*SetList, error) {
|
||||
}
|
||||
|
||||
//We Read the response body on the line below.
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
return &SetList{}, err
|
||||
@ -88,16 +88,17 @@ func ShowSet(setname string) error {
|
||||
|
||||
client := storageConnect()
|
||||
coll := &Collection{client.Database("serra").Collection("cards")}
|
||||
l := Logger()
|
||||
defer storageDisconnect(client)
|
||||
|
||||
// fetch all cards in set ordered by currently used currency
|
||||
cardSortCurrency := bson.D{{"prices.usd", -1}}
|
||||
if getCurrency() == "EUR" {
|
||||
if getCurrency() == EUR {
|
||||
cardSortCurrency = bson.D{{"prices.eur", -1}}
|
||||
}
|
||||
cards, err := coll.storageFind(bson.D{{"set", setname}}, cardSortCurrency, 0, 0)
|
||||
if (err != nil) || len(cards) == 0 {
|
||||
LogMessage(fmt.Sprintf("Error: Set %s not found or no card in your collection.", setname), "red")
|
||||
l.Errorf("Set %s not found or no card in your collection.", setname)
|
||||
return err
|
||||
}
|
||||
|
||||
@ -142,7 +143,7 @@ func ShowSet(setname string) error {
|
||||
|
||||
ri := convertRarities(rar)
|
||||
|
||||
LogMessage(sets[0].Name, "green")
|
||||
fmt.Printf("%s%s%s\n", Green, sets[0].Name, Reset)
|
||||
fmt.Printf("Released: %s\n", sets[0].ReleasedAt)
|
||||
fmt.Printf("Set Cards: %d/%d\n", len(cards), sets[0].CardCount)
|
||||
fmt.Printf("Total Cards: %.0f\n", stats[0]["count"])
|
||||
@ -150,12 +151,12 @@ func ShowSet(setname string) error {
|
||||
|
||||
normalValue, err := getFloat64(stats[0]["value"])
|
||||
if err != nil {
|
||||
LogMessage(fmt.Sprintf("Error: %v", err), "red")
|
||||
l.Error(err)
|
||||
normalValue = 0
|
||||
}
|
||||
foilValue, err := getFloat64(stats[0]["value_foil"])
|
||||
if err != nil {
|
||||
LogMessage(fmt.Sprintf("Error: %v", err), "red")
|
||||
l.Error(err)
|
||||
foilValue = 0
|
||||
}
|
||||
totalValue := normalValue + foilValue
|
||||
245
pkg/serra/stats.go
Normal file
245
pkg/serra/stats.go
Normal file
@ -0,0 +1,245 @@
|
||||
package serra
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/spf13/cobra"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(statsCmd)
|
||||
}
|
||||
|
||||
var statsCmd = &cobra.Command{
|
||||
Aliases: []string{"stats"},
|
||||
Use: "stats",
|
||||
Short: "Shows statistics of the collection",
|
||||
SilenceErrors: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
Stats()
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func Stats() {
|
||||
client := storageConnect()
|
||||
coll := &Collection{client.Database("serra").Collection("cards")}
|
||||
totalcoll := &Collection{client.Database("serra").Collection("total")}
|
||||
defer storageDisconnect(client)
|
||||
|
||||
// Show Value Stats
|
||||
showValueStats(coll, totalcoll)
|
||||
|
||||
// Reserved List
|
||||
showReservedListStats(coll)
|
||||
|
||||
// Rarities
|
||||
showRarityStats(coll)
|
||||
|
||||
// Colors
|
||||
showColorStats(coll)
|
||||
|
||||
// Artists
|
||||
showArtistStats(coll)
|
||||
|
||||
// Mana Curve of Collection
|
||||
showManaCurveStats(coll)
|
||||
|
||||
// Show cards added per month
|
||||
showCardsAddedPerMonth(coll)
|
||||
}
|
||||
|
||||
func showValueStats(coll *Collection, totalcoll *Collection) {
|
||||
l := Logger()
|
||||
// Value and Card Numbers
|
||||
stats, _ := coll.storageAggregate(mongo.Pipeline{
|
||||
bson.D{
|
||||
{"$group", bson.D{
|
||||
{"_id", nil},
|
||||
{"value", bson.D{{"$sum", bson.D{{"$multiply", bson.A{getCurrencyField(false), "$serra_count"}}}}}},
|
||||
{"value_foil", bson.D{{"$sum", bson.D{{"$multiply", bson.A{getCurrencyField(true), "$serra_count_foil"}}}}}},
|
||||
{"count", bson.D{{"$sum", bson.D{{"$multiply", bson.A{1.0, "$serra_count"}}}}}},
|
||||
{"count_foil", bson.D{{"$sum", "$serra_count_foil"}}},
|
||||
{"rarity", bson.D{{"$sum", "$rarity"}}},
|
||||
{"unique", bson.D{{"$sum", 1}}},
|
||||
}},
|
||||
},
|
||||
bson.D{
|
||||
{"$addFields", bson.D{
|
||||
{"count_all", bson.D{{"$sum", bson.A{"$count", "$count_foil"}}}},
|
||||
}},
|
||||
},
|
||||
})
|
||||
fmt.Printf("%sCards %s\n", Green, Reset)
|
||||
fmt.Printf("Total: %s%.0f%s\n", Yellow, stats[0]["count_all"], Reset)
|
||||
fmt.Printf("Unique: %s%d%s\n", Purple, stats[0]["unique"], Reset)
|
||||
fmt.Printf("Normal: %s%.0f%s\n", Purple, stats[0]["count"], Reset)
|
||||
fmt.Printf("Foil: %s%d%s\n", Purple, stats[0]["count_foil"], Reset)
|
||||
|
||||
// Total Value
|
||||
fmt.Printf("\n%sTotal Value%s\n", Green, Reset)
|
||||
normalValue, err := getFloat64(stats[0]["value"])
|
||||
if err != nil {
|
||||
l.Error(err)
|
||||
normalValue = 0
|
||||
}
|
||||
foilValue, err := getFloat64(stats[0]["value_foil"])
|
||||
if err != nil {
|
||||
l.Error(err)
|
||||
foilValue = 0
|
||||
}
|
||||
countAll, err := getFloat64(stats[0]["count_all"])
|
||||
if err != nil {
|
||||
l.Error(err)
|
||||
foilValue = 0
|
||||
}
|
||||
totalValue := normalValue + foilValue
|
||||
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("Foils: %s%.2f%s%s\n", Pink, foilValue, getCurrency(), Reset)
|
||||
fmt.Printf("Average Card: %s%.2f%s%s\n", Pink, totalValue/countAll, getCurrency(), Reset)
|
||||
total, _ := totalcoll.storageFindTotal()
|
||||
|
||||
fmt.Printf("History: \n")
|
||||
showPriceHistory(total.Value, "* ", true)
|
||||
}
|
||||
|
||||
func showReservedListStats(coll *Collection) {
|
||||
reserved, _ := coll.storageAggregate(mongo.Pipeline{
|
||||
bson.D{
|
||||
{"$match", bson.D{
|
||||
{"reserved", true}}}},
|
||||
bson.D{
|
||||
{"$group", bson.D{
|
||||
{"_id", nil},
|
||||
{"count", bson.D{{"$sum", 1}}},
|
||||
}}},
|
||||
})
|
||||
|
||||
var countReserved int32
|
||||
if len(reserved) > 0 {
|
||||
countReserved = reserved[0]["count"].(int32)
|
||||
}
|
||||
fmt.Printf("Reserved List: %s%d%s\n", Yellow, countReserved, Reset)
|
||||
}
|
||||
|
||||
func showRarityStats(coll *Collection) {
|
||||
rar, _ := coll.storageAggregate(mongo.Pipeline{
|
||||
bson.D{
|
||||
{"$group", bson.D{
|
||||
{"_id", "$rarity"},
|
||||
{"count", bson.D{{"$sum", bson.D{{"$multiply", bson.A{1.0, "$serra_count"}}}}}},
|
||||
}}},
|
||||
bson.D{
|
||||
{"$sort", bson.D{
|
||||
{"_id", 1},
|
||||
}}},
|
||||
})
|
||||
ri := convertRarities(rar)
|
||||
fmt.Printf("\n%sRarity%s\n", Green, Reset)
|
||||
fmt.Printf("Mythics: %s%.0f%s\n", Pink, ri.Mythics, Reset)
|
||||
fmt.Printf("Rares: %s%.0f%s\n", Pink, ri.Rares, Reset)
|
||||
fmt.Printf("Uncommons: %s%.0f%s\n", Yellow, ri.Uncommons, Reset)
|
||||
fmt.Printf("Commons: %s%.0f%s\n", Purple, ri.Commons, Reset)
|
||||
}
|
||||
|
||||
func showCardsAddedPerMonth(coll *Collection) {
|
||||
fmt.Printf("\n%sCards added over time%s\n", Green, Reset)
|
||||
type Caot struct {
|
||||
ID struct {
|
||||
Year int32 `mapstructure:"year"`
|
||||
Month int32 `mapstructure:"month"`
|
||||
} `mapstructure:"_id"`
|
||||
Count int32 `mapstructure:"count"`
|
||||
}
|
||||
caot, _ := coll.storageAggregate(mongo.Pipeline{
|
||||
bson.D{
|
||||
{"$project", bson.D{
|
||||
{"month", bson.D{
|
||||
{"$month", "$serra_created"}}},
|
||||
{"year", bson.D{
|
||||
{"$year", "$serra_created"}},
|
||||
}},
|
||||
}},
|
||||
bson.D{
|
||||
{"$group", bson.D{
|
||||
{"_id", bson.D{{"month", "$month"}, {"year", "$year"}}},
|
||||
{"count", bson.D{{"$sum", 1}}},
|
||||
}},
|
||||
},
|
||||
bson.D{
|
||||
{"$sort", bson.D{{"_id.year", 1}, {"_id.month", 1}}},
|
||||
},
|
||||
})
|
||||
for _, mo := range caot {
|
||||
moo := new(Caot)
|
||||
mapstructure.Decode(mo, moo)
|
||||
fmt.Printf("%d-%02d: %s%d%s\n", moo.ID.Year, moo.ID.Month, Purple, moo.Count, Reset)
|
||||
}
|
||||
}
|
||||
|
||||
func showManaCurveStats(coll *Collection) {
|
||||
cmc, _ := coll.storageAggregate(mongo.Pipeline{
|
||||
bson.D{
|
||||
{"$group", bson.D{
|
||||
{"_id", "$cmc"},
|
||||
{"count", bson.D{{"$sum", 1}}},
|
||||
}}},
|
||||
bson.D{
|
||||
{"$sort", bson.D{
|
||||
{"_id", 1},
|
||||
}}},
|
||||
})
|
||||
fmt.Printf("\n%sMana Curve%s\n", Green, Reset)
|
||||
for _, mc := range cmc {
|
||||
fmt.Printf("%.0f: %s%d%s\n", mc["_id"], Purple, mc["count"], Reset)
|
||||
}
|
||||
}
|
||||
|
||||
func showArtistStats(coll *Collection) {
|
||||
artists, _ := coll.storageAggregate(mongo.Pipeline{
|
||||
bson.D{
|
||||
{"$group", bson.D{
|
||||
{"_id", "$artist"},
|
||||
{"count", bson.D{{"$sum", 1}}},
|
||||
}}},
|
||||
bson.D{
|
||||
{"$sort", bson.D{
|
||||
{"count", -1},
|
||||
}}},
|
||||
bson.D{
|
||||
{"$limit", 10}},
|
||||
})
|
||||
fmt.Printf("\n%sTop Artists%s\n", Green, Reset)
|
||||
for _, artist := range artists {
|
||||
fmt.Printf("%s: %s%d%s\n", artist["_id"].(string), Purple, artist["count"], Reset)
|
||||
}
|
||||
}
|
||||
|
||||
func showColorStats(coll *Collection) {
|
||||
sets, _ := coll.storageAggregate(mongo.Pipeline{
|
||||
bson.D{
|
||||
{"$match", bson.D{
|
||||
{"coloridentity", bson.D{{"$size", 1}}}}}},
|
||||
bson.D{
|
||||
{"$group", bson.D{
|
||||
{"_id", "$coloridentity"},
|
||||
{"count", bson.D{{"$sum", bson.D{{"$multiply", bson.A{1.0, "$serra_count"}}}}}},
|
||||
}}},
|
||||
bson.D{
|
||||
{"$sort", bson.D{
|
||||
{"count", -1},
|
||||
}}},
|
||||
})
|
||||
|
||||
fmt.Printf("\n%sColors%s\n", Green, Reset)
|
||||
for _, set := range sets {
|
||||
x, _ := set["_id"].(primitive.A)
|
||||
s := []interface{}(x)
|
||||
fmt.Printf("%s: %s%.0f%s\n", convertManaSymbols(s), Purple, set["count"], Reset)
|
||||
}
|
||||
}
|
||||
@ -3,7 +3,6 @@ package serra
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
@ -18,7 +17,8 @@ type Total struct {
|
||||
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 {
|
||||
*mongo.Collection
|
||||
}
|
||||
@ -46,12 +46,12 @@ func getCurrencyField(foil bool) string {
|
||||
}
|
||||
|
||||
func storageConnect() *mongo.Client {
|
||||
l := Logger()
|
||||
uri := getMongoDBURI()
|
||||
|
||||
client, err := mongo.Connect(context.TODO(), options.Client().ApplyURI(uri))
|
||||
if err != nil {
|
||||
LogMessage(fmt.Sprintf("Could not connect to mongodb at %s", uri), "red")
|
||||
os.Exit(1)
|
||||
l.Fatalf("Could not connect to mongodb at %s", uri)
|
||||
}
|
||||
|
||||
return client
|
||||
@ -104,14 +104,15 @@ func (coll Collection) storageAddTotal(p PriceEntry) error {
|
||||
func (coll Collection) storageFind(filter, sort bson.D, skip, limit int64) ([]Card, error) {
|
||||
opts := options.Find().SetSort(sort).SetSkip(skip).SetLimit(limit)
|
||||
cursor, err := coll.Find(context.TODO(), filter, opts)
|
||||
l := Logger()
|
||||
|
||||
if err != nil {
|
||||
LogMessage(fmt.Sprintf("Could not query data due to connection errors to database: %s", err.Error()), "red")
|
||||
os.Exit(1)
|
||||
l.Fatalf("Could not query data due to connection errors to database: %s", err.Error())
|
||||
}
|
||||
|
||||
var results []Card
|
||||
if err = cursor.All(context.TODO(), &results); err != nil {
|
||||
log.Fatal(err)
|
||||
l.Fatal(err)
|
||||
return []Card{}, err
|
||||
}
|
||||
return results, nil
|
||||
@ -119,17 +120,17 @@ func (coll Collection) storageFind(filter, sort bson.D, skip, limit int64) ([]Ca
|
||||
}
|
||||
|
||||
func (coll Collection) storageFindSet(filter, sort bson.D) ([]Set, error) {
|
||||
l := Logger()
|
||||
opts := options.Find().SetSort(sort)
|
||||
|
||||
cursor, err := coll.Find(context.TODO(), filter, opts)
|
||||
if err != nil {
|
||||
LogMessage(fmt.Sprintf("Could not query set data due to connection errors to database: %s", err.Error()), "red")
|
||||
os.Exit(1)
|
||||
l.Fatalf("Could not query set data due to connection errors to database: %s", err.Error())
|
||||
}
|
||||
|
||||
var results []Set
|
||||
if err = cursor.All(context.TODO(), &results); err != nil {
|
||||
log.Fatal(err)
|
||||
l.Fatal(err)
|
||||
return []Set{}, err
|
||||
}
|
||||
|
||||
@ -138,27 +139,29 @@ func (coll Collection) storageFindSet(filter, sort bson.D) ([]Set, error) {
|
||||
|
||||
func (coll Collection) storageFindTotal() (Total, error) {
|
||||
var total Total
|
||||
l := Logger()
|
||||
|
||||
err := coll.FindOne(context.TODO(), bson.D{{"_id", "1"}}).Decode(&total)
|
||||
if err != nil {
|
||||
LogMessage(fmt.Sprintf("Could not query total data due to connection errors to database: %s", err.Error()), "red")
|
||||
os.Exit(1)
|
||||
l.Fatalf("Could not query total data due to connection errors to database: %s", err.Error())
|
||||
}
|
||||
|
||||
return total, nil
|
||||
}
|
||||
|
||||
func (coll Collection) storageRemove(filter bson.M) error {
|
||||
l := Logger()
|
||||
|
||||
_, err := coll.DeleteOne(context.TODO(), filter)
|
||||
if err != nil {
|
||||
LogMessage(fmt.Sprintf("Could remove card data due to connection errors to database: %s", err.Error()), "red")
|
||||
os.Exit(1)
|
||||
l.Fatalf("Could remove card data due to connection errors to database: %s", err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (coll Collection) storageAggregate(pipeline mongo.Pipeline) ([]primitive.M, error) {
|
||||
l := Logger()
|
||||
opts := options.Aggregate()
|
||||
|
||||
cursor, err := coll.Aggregate(
|
||||
@ -166,21 +169,21 @@ func (coll Collection) storageAggregate(pipeline mongo.Pipeline) ([]primitive.M,
|
||||
pipeline,
|
||||
opts)
|
||||
if err != nil {
|
||||
LogMessage(fmt.Sprintf("Could not aggregate data due to connection errors to database: %s", err.Error()), "red")
|
||||
os.Exit(1)
|
||||
l.Fatalf("Could not aggregate data due to connection errors to database: %s", err.Error())
|
||||
}
|
||||
|
||||
// Get a list of all returned documents and print them out.
|
||||
// See the mongo.Cursor documentation for more examples of using cursors.
|
||||
var results []bson.M
|
||||
if err = cursor.All(context.TODO(), &results); err != nil {
|
||||
log.Fatal(err)
|
||||
l.Fatal(err)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (coll Collection) storageUpdate(filter, update bson.M) error {
|
||||
l := Logger()
|
||||
// Call the driver's UpdateOne() method and pass filter and update to it
|
||||
_, err := coll.UpdateOne(
|
||||
context.Background(),
|
||||
@ -188,8 +191,7 @@ func (coll Collection) storageUpdate(filter, update bson.M) error {
|
||||
update,
|
||||
)
|
||||
if err != nil {
|
||||
LogMessage(fmt.Sprintf("Could not update data due to connection errors to database: %s", err.Error()), "red")
|
||||
os.Exit(1)
|
||||
l.Fatalf("Could not update data due to connection errors to database: %s", err.Error())
|
||||
}
|
||||
|
||||
return 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
|
||||
},
|
||||
}
|
||||
@ -19,12 +19,13 @@ func init() {
|
||||
var updateCmd = &cobra.Command{
|
||||
Aliases: []string{"u"},
|
||||
Use: "update",
|
||||
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.`,
|
||||
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.`,
|
||||
SilenceErrors: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
client := storageConnect()
|
||||
l := Logger()
|
||||
defer storageDisconnect(client)
|
||||
|
||||
// update sets
|
||||
@ -32,6 +33,7 @@ var updateCmd = &cobra.Command{
|
||||
coll := &Collection{client.Database("serra").Collection("cards")}
|
||||
totalcoll := &Collection{client.Database("serra").Collection("total")}
|
||||
|
||||
// predefine query for set analysis. used for total stats later
|
||||
projectStage := bson.D{{"$project",
|
||||
bson.D{
|
||||
{"serra_count", true},
|
||||
@ -47,11 +49,35 @@ var updateCmd = &cobra.Command{
|
||||
{"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()
|
||||
for _, set := range sets.Data {
|
||||
|
||||
// When downloading new sets, PriceList needs to be initialized
|
||||
// This query silently fails if set was already downloaded. Not nice but ok for now.
|
||||
// TODO: make this not fail silently
|
||||
set.SerraPrices = []PriceEntry{}
|
||||
setscoll.storageAddSet(&set)
|
||||
|
||||
@ -72,21 +98,22 @@ var updateCmd = &cobra.Command{
|
||||
SaucerHead: "[green]>[reset]",
|
||||
SaucerPadding: " ",
|
||||
BarStart: "|",
|
||||
BarEnd: fmt.Sprintf("| %s%s%s", Pink, set.Name, Reset),
|
||||
BarEnd: "| " + set.Name,
|
||||
}),
|
||||
)
|
||||
|
||||
for _, card := range cards {
|
||||
bar.Add(1)
|
||||
updatedCard, err := fetchCard(card.Set, card.CollectorNumber)
|
||||
updatedCard, err := getCardFromBulk(updatedCards, card.Set, card.CollectorNumber)
|
||||
if err != nil {
|
||||
LogMessage(fmt.Sprintf("%v", err), "red")
|
||||
l.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
updatedCard.Prices.Date = primitive.NewDateTimeFromTime(time.Now())
|
||||
|
||||
update := bson.M{
|
||||
"$set": bson.M{"serra_updated": primitive.NewDateTimeFromTime(time.Now()), "prices": updatedCard.Prices, "cmc": updatedCard.Cmc},
|
||||
"$set": bson.M{"serra_updated": primitive.NewDateTimeFromTime(time.Now()), "prices": updatedCard.Prices, "cmc": updatedCard.Cmc, "cardmarketid": updatedCard.CardmarketID, "tcgplayerid": updatedCard.TCGPlayerID},
|
||||
"$push": bson.M{"serra_prices": updatedCard.Prices},
|
||||
}
|
||||
coll.storageUpdate(bson.M{"_id": bson.M{"$eq": card.ID}}, update)
|
||||
@ -109,10 +136,9 @@ var updateCmd = &cobra.Command{
|
||||
|
||||
// do the update
|
||||
setUpdate := bson.M{
|
||||
"$set": bson.M{"serra_updated": p.Date, "card_count": set.CardCount},
|
||||
"$set": bson.M{"serra_updated": p.Date, "cardcount": set.CardCount},
|
||||
"$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)
|
||||
}
|
||||
|
||||
@ -127,7 +153,7 @@ var updateCmd = &cobra.Command{
|
||||
tmpCard := Card{}
|
||||
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)
|
||||
|
||||
return nil
|
||||
@ -3,6 +3,7 @@ package serra
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"text/template"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/spf13/cobra"
|
||||
@ -16,6 +17,10 @@ func init() {
|
||||
rootCmd.AddCommand(webCmd)
|
||||
}
|
||||
|
||||
func add(a, b int64) int64 {
|
||||
return a + b
|
||||
}
|
||||
|
||||
var webCmd = &cobra.Command{
|
||||
Aliases: []string{"a"},
|
||||
Use: "web",
|
||||
@ -38,6 +43,9 @@ type Query struct {
|
||||
|
||||
func startWeb() error {
|
||||
router := gin.Default()
|
||||
router.SetFuncMap(template.FuncMap{
|
||||
"add": add,
|
||||
})
|
||||
router.LoadHTMLGlob("templates/*.tmpl")
|
||||
router.Static("/assets", "./assets")
|
||||
|
||||
@ -63,7 +71,7 @@ func landingPage(c *gin.Context) {
|
||||
sets := Sets("release")
|
||||
|
||||
// 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
|
||||
filter := bson.D{}
|
||||
@ -89,7 +97,12 @@ func landingPage(c *gin.Context) {
|
||||
}}},
|
||||
})
|
||||
defer storageDisconnect(client)
|
||||
numCards := counts[0]["count"].(int32)
|
||||
|
||||
// Catch index error on no results
|
||||
var numCards int32
|
||||
if len(counts) != 0 {
|
||||
numCards = counts[0]["count"].(int32)
|
||||
}
|
||||
|
||||
c.HTML(http.StatusOK, "index.tmpl", gin.H{
|
||||
"title": "Serra",
|
||||
26
readme.md
26
readme.md
@ -196,7 +196,31 @@ No extra steps needed. Only new Webinterface and Foil support
|
||||
In this stage of the development of serra, I was breaking the original
|
||||
database "schema" without migration.
|
||||
|
||||
Sadly you need to export `utils/` Folder in this repo
|
||||
Sadly you need to export the cards from the mongodb and import it again using `serra add ` commands
|
||||
|
||||
I wrote a little helper script in python to export all the cards in format set/number and generate some queries
|
||||
|
||||
```
|
||||
python3 export.py > add_commands.sh
|
||||
|
||||
head add_commands.sh
|
||||
./serra add 5ed/3 -c 1
|
||||
./serra add mmq/2 -c 1
|
||||
./serra add p02/4 -c 1
|
||||
./serra add chr/44 -c 1
|
||||
./serra add 4ed/291 -c 1
|
||||
./serra add 4ed/292 -c 1
|
||||
./serra add mir/2 -c 1
|
||||
./serra add usg/231 -c 1
|
||||
./serra add mir/155 -c 1
|
||||
./serra add pcy/29 -c 2
|
||||
|
||||
<do the upgrade of serra (download new binary>
|
||||
|
||||
<delete the old mongodb or just empty it completly>
|
||||
|
||||
bash add_commands.sh
|
||||
```
|
||||
|
||||
# Development
|
||||
|
||||
|
||||
15
scripts/export.py
Normal file
15
scripts/export.py
Normal file
@ -0,0 +1,15 @@
|
||||
from pymongo import MongoClient
|
||||
import os
|
||||
import pymongo
|
||||
|
||||
CONNECTION_STRING = os.getenv("MONGODB_URI")
|
||||
client = MongoClient(CONNECTION_STRING+'/admin')
|
||||
|
||||
|
||||
# Create a new collection
|
||||
collection = client["serra"]["cards"]
|
||||
|
||||
cards=collection.find()
|
||||
|
||||
for c in cards:
|
||||
print("./serra add %s/%s -c %s" % (c["set"], c["collectornumber"], c["serra_count"]))
|
||||
19
scripts/removebulk.fish
Executable file
19
scripts/removebulk.fish
Executable file
@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env fish
|
||||
set SET $argv[1]
|
||||
|
||||
|
||||
# export and remove colors
|
||||
serra cards --set $SET --min-count 2 --sort value | gsed 's/\x1B[@A-Z\\\]^_]\|\x1B\[[0-9:;<=>?]*[-!"#$%&'"'"'()*+,.\/]*[][\\@A-Z^_`a-z{|}~]//g' > $SET
|
||||
|
||||
# edit
|
||||
nvim $SET
|
||||
|
||||
# remove formatting
|
||||
cat $SET | gsed 's/^\* //' | gsed 's/x.*(/ /' | gsed 's/).*//' | grep -v "Total Value" > {$SET}.txt
|
||||
|
||||
# delete everything from serra
|
||||
for x in (cat {$SET}.txt) ; echo serra remove -c $x ; end
|
||||
|
||||
# cleanup
|
||||
rm $SET
|
||||
rm {$SET}.txt
|
||||
@ -1,170 +0,0 @@
|
||||
package serra
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(statsCmd)
|
||||
}
|
||||
|
||||
var statsCmd = &cobra.Command{
|
||||
Aliases: []string{"stats"},
|
||||
Use: "stats <prefix> <n>",
|
||||
Short: "Shows statistics of the collection",
|
||||
SilenceErrors: true,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
client := storageConnect()
|
||||
coll := &Collection{client.Database("serra").Collection("cards")}
|
||||
totalcoll := &Collection{client.Database("serra").Collection("total")}
|
||||
defer storageDisconnect(client)
|
||||
|
||||
// Colors
|
||||
sets, _ := coll.storageAggregate(mongo.Pipeline{
|
||||
bson.D{
|
||||
{"$match", bson.D{
|
||||
{"coloridentity", bson.D{{"$size", 1}}}}}},
|
||||
bson.D{
|
||||
{"$group", bson.D{
|
||||
{"_id", "$coloridentity"},
|
||||
{"count", bson.D{{"$sum", bson.D{{"$multiply", bson.A{1.0, "$serra_count"}}}}}},
|
||||
}}},
|
||||
bson.D{
|
||||
{"$sort", bson.D{
|
||||
{"count", -1},
|
||||
}}},
|
||||
})
|
||||
fmt.Printf("%sColors%s\n", Green, Reset)
|
||||
for _, set := range sets {
|
||||
x, _ := set["_id"].(primitive.A)
|
||||
s := []interface{}(x)
|
||||
fmt.Printf("%s: %s%.0f%s\n", convertManaSymbols(s), Purple, set["count"], Reset)
|
||||
}
|
||||
|
||||
// Value and Card Numbers
|
||||
stats, _ := coll.storageAggregate(mongo.Pipeline{
|
||||
bson.D{
|
||||
{"$group", bson.D{
|
||||
{"_id", nil},
|
||||
{"value", bson.D{{"$sum", bson.D{{"$multiply", bson.A{getCurrencyField(false), "$serra_count"}}}}}},
|
||||
{"value_foil", bson.D{{"$sum", bson.D{{"$multiply", bson.A{getCurrencyField(true), "$serra_count_foil"}}}}}},
|
||||
{"count", bson.D{{"$sum", bson.D{{"$multiply", bson.A{1.0, "$serra_count"}}}}}},
|
||||
{"count_foil", bson.D{{"$sum", "$serra_count_foil"}}},
|
||||
{"rarity", bson.D{{"$sum", "$rarity"}}},
|
||||
{"unique", bson.D{{"$sum", 1}}},
|
||||
}},
|
||||
},
|
||||
bson.D{
|
||||
{"$addFields", bson.D{
|
||||
{"count_all", bson.D{{"$sum", bson.A{"$count", "$count_foil"}}}},
|
||||
}},
|
||||
},
|
||||
})
|
||||
fmt.Printf("\n%sCards %s\n", Green, Reset)
|
||||
fmt.Printf("Total: %s%.0f%s\n", Yellow, stats[0]["count_all"], Reset)
|
||||
fmt.Printf("Unique: %s%d%s\n", Purple, stats[0]["unique"], Reset)
|
||||
fmt.Printf("Normal: %s%.0f%s\n", Purple, stats[0]["count"], Reset)
|
||||
fmt.Printf("Foil: %s%d%s\n", Purple, stats[0]["count_foil"], Reset)
|
||||
|
||||
reserved, _ := coll.storageAggregate(mongo.Pipeline{
|
||||
bson.D{
|
||||
{"$match", bson.D{
|
||||
{"reserved", true}}}},
|
||||
bson.D{
|
||||
{"$group", bson.D{
|
||||
{"_id", nil},
|
||||
{"count", bson.D{{"$sum", 1}}},
|
||||
}}},
|
||||
})
|
||||
|
||||
var count_reserved int32
|
||||
if len(reserved) > 0 {
|
||||
count_reserved = reserved[0]["count"].(int32)
|
||||
}
|
||||
fmt.Printf("Reserved List: %s%d%s\n", Yellow, count_reserved, Reset)
|
||||
|
||||
// Rarities
|
||||
rar, _ := coll.storageAggregate(mongo.Pipeline{
|
||||
bson.D{
|
||||
{"$group", bson.D{
|
||||
{"_id", "$rarity"},
|
||||
{"count", bson.D{{"$sum", bson.D{{"$multiply", bson.A{1.0, "$serra_count"}}}}}},
|
||||
}}},
|
||||
bson.D{
|
||||
{"$sort", bson.D{
|
||||
{"_id", 1},
|
||||
}}},
|
||||
})
|
||||
ri := convertRarities(rar)
|
||||
fmt.Printf("\n%sRarity%s\n", Green, Reset)
|
||||
fmt.Printf("Mythics: %s%.0f%s\n", Pink, ri.Mythics, Reset)
|
||||
fmt.Printf("Rares: %s%.0f%s\n", Pink, ri.Rares, Reset)
|
||||
fmt.Printf("Uncommons: %s%.0f%s\n", Yellow, ri.Uncommons, Reset)
|
||||
fmt.Printf("Commons: %s%.0f%s\n", Purple, ri.Commons, Reset)
|
||||
|
||||
// Artists
|
||||
artists, _ := coll.storageAggregate(mongo.Pipeline{
|
||||
bson.D{
|
||||
{"$group", bson.D{
|
||||
{"_id", "$artist"},
|
||||
{"count", bson.D{{"$sum", 1}}},
|
||||
}}},
|
||||
bson.D{
|
||||
{"$sort", bson.D{
|
||||
{"count", -1},
|
||||
}}},
|
||||
bson.D{
|
||||
{"$limit", 10}},
|
||||
})
|
||||
fmt.Printf("\n%sTop Artists%s\n", Green, Reset)
|
||||
for _, artist := range artists {
|
||||
fmt.Printf("%s: %s%d%s\n", artist["_id"].(string), Purple, artist["count"], Reset)
|
||||
}
|
||||
|
||||
// Mana Curve of Collection
|
||||
cmc, _ := coll.storageAggregate(mongo.Pipeline{
|
||||
bson.D{
|
||||
{"$group", bson.D{
|
||||
{"_id", "$cmc"},
|
||||
// {"count", bson.D{{"$sum", bson.D{{"$multiply", bson.A{1.0, "$serra_count"}}}}}},
|
||||
{"count", bson.D{{"$sum", 1}}},
|
||||
}}},
|
||||
bson.D{
|
||||
{"$sort", bson.D{
|
||||
{"_id", 1},
|
||||
}}},
|
||||
})
|
||||
fmt.Printf("\n%sMana Curve%s\n", Green, Reset)
|
||||
for _, mc := range cmc {
|
||||
fmt.Printf("%.0f: %s%d%s\n", mc["_id"], Purple, mc["count"], Reset)
|
||||
}
|
||||
|
||||
// Total Value
|
||||
fmt.Printf("\n%sTotal Value%s\n", Green, Reset)
|
||||
normalValue, err := getFloat64(stats[0]["value"])
|
||||
if err != nil {
|
||||
LogMessage(fmt.Sprintf("Error: %v", err), "red")
|
||||
normalValue = 0
|
||||
}
|
||||
foilValue, err := getFloat64(stats[0]["value_foil"])
|
||||
if err != nil {
|
||||
LogMessage(fmt.Sprintf("Error: %v", err), "red")
|
||||
foilValue = 0
|
||||
}
|
||||
totalValue := normalValue + foilValue
|
||||
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("Foils: %s%.2f%s%s\n", Pink, foilValue, getCurrency(), Reset)
|
||||
total, _ := totalcoll.storageFindTotal()
|
||||
|
||||
fmt.Printf("History: \n")
|
||||
showPriceHistory(total.Value, "* ", true)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@ -1,32 +0,0 @@
|
||||
package serra
|
||||
|
||||
import "fmt"
|
||||
|
||||
var (
|
||||
Icon = "\U0001F9D9\U0001F3FC"
|
||||
Reset = "\033[0m"
|
||||
Background = "\033[38;5;59m"
|
||||
CurrentLine = "\033[38;5;60m"
|
||||
Foreground = "\033[38;5;231m"
|
||||
Comment = "\033[38;5;103m"
|
||||
Cyan = "\033[38;5;159m"
|
||||
Green = "\033[38;5;120m"
|
||||
Orange = "\033[38;5;222m"
|
||||
Pink = "\033[38;5;212m"
|
||||
Purple = "\033[38;5;183m"
|
||||
Red = "\033[38;5;210m"
|
||||
Yellow = "\033[38;5;229m"
|
||||
)
|
||||
|
||||
// Colored output on commandline
|
||||
func LogMessage(message string, color string) {
|
||||
if color == "red" {
|
||||
fmt.Printf("%s%s%s\n", Red, message, Reset)
|
||||
} else if color == "green" {
|
||||
fmt.Printf("%s%s%s\n", Green, message, Reset)
|
||||
} else if color == "purple" {
|
||||
fmt.Printf("%s%s%s\n", Purple, message, Reset)
|
||||
} else {
|
||||
fmt.Printf("%s\n", message)
|
||||
}
|
||||
}
|
||||
@ -4,7 +4,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Serra</title>
|
||||
<title>{{.title}}{{ if .query.Set }} - Set: {{.query.Set}}{{end}}</title>
|
||||
<link rel="stylesheet" href="https://jenil.github.io/bulmaswatch/cosmo/bulmaswatch.min.css">
|
||||
<!-- <link rel="stylesheet" href="https://jenil.github.io/bulmaswatch/lumen/bulmaswatch.min.css"> -->
|
||||
<!-- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css"> -->
|
||||
@ -67,7 +67,7 @@
|
||||
<section class="hero is-black">
|
||||
<div class="hero-body">
|
||||
<p class="title">
|
||||
{{ .title }}
|
||||
<a href="/">{{ .title }}</a>
|
||||
</p>
|
||||
<p class="subtitle">
|
||||
<i>Magic: The Gathering</i> Collection
|
||||
@ -168,7 +168,7 @@
|
||||
<tbody>
|
||||
{{range .cards}}
|
||||
<tr>
|
||||
<td>{{.SerraCount}}</td>
|
||||
<td>{{ add .SerraCount .SerraCountFoil }}</td>
|
||||
<td>
|
||||
<div class="cardpreview"><strong>{{.Name }}</strong>
|
||||
<span class="cardpreviewtext">
|
||||
@ -247,17 +247,14 @@
|
||||
<script>
|
||||
function getParam(paramName) {
|
||||
return decodeURI(
|
||||
(RegExp(paramName + '=' + '(.+?)(&|$)').exec(location.search) || [, null])[1]
|
||||
(RegExp(paramName + '=' + '(.+?)(&|$)').exec(location.search) || [, 500])[1]
|
||||
);
|
||||
}
|
||||
var selectedSetVal = getParam("set");
|
||||
document.getElementById("set").value = selectedSetVal;
|
||||
|
||||
var selectedSetVal = getParam("page");
|
||||
document.getElementById("page").value = selectedSetVal;
|
||||
|
||||
var selectedSetVal = getParam("limit");
|
||||
document.getElementById("limit").value = selectedSetVal;
|
||||
var selectedLimitVal = getParam("limit");
|
||||
document.getElementById("limit").value = selectedLimitVal;
|
||||
|
||||
var selectedSortVal = getParam("sort");
|
||||
document.getElementById("sort").value = selectedSortVal;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user