Compare commits
194 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 | ||
|
|
4580748ebe | ||
|
|
5eac08c40b | ||
|
|
5f3d643b92 | ||
|
|
924c222fe5 | ||
|
|
697365c518 | ||
|
|
f34aa42e8f | ||
|
|
5f135d07cb | ||
|
|
8ccaafc140 | ||
|
|
0140463e09 | ||
|
|
d1dac680e0 | ||
|
|
bb823aeb20 | ||
|
|
d704528d8e | ||
|
|
c6e35c48c9 | ||
|
|
c20f84562e | ||
|
|
510e7fe9da | ||
|
|
f0fc422e30 | ||
|
|
73e982ab92 | ||
|
|
d1fd624ba8 | ||
|
|
71f451bbd1 | ||
|
|
d293586eba | ||
|
|
d03931c091 | ||
|
|
030bc2c3d8 | ||
|
|
af4af39adc | ||
|
|
7b6addcb05 | ||
|
|
578ad203ca | ||
|
|
d9bb463be9 | ||
|
|
104b9b6e96 | ||
|
|
af8a6af2ec | ||
|
|
27be6bf772 | ||
|
|
339cb56eb2 | ||
|
|
03a15d998e | ||
|
|
40b08d9846 | ||
|
|
6965317ea5 | ||
|
|
8e22505ca0 | ||
|
|
fa59a96db2 | ||
|
|
38ffc25b44 | ||
|
|
96731b8fe0 | ||
|
|
755b333bbb | ||
|
|
ba6ae6db71 | ||
|
|
040561c462 | ||
|
|
f2f6317234 | ||
|
|
8258bca36b | ||
|
|
e4b5f87be8 | ||
|
|
637d385e74 | ||
|
|
2ed9467f6a | ||
|
|
e0d90ae12d | ||
|
|
c784477241 | ||
|
|
4a1147b1af | ||
|
|
c106a01a28 | ||
|
|
0d51645516 | ||
|
|
927c568059 | ||
|
|
5c3c06d1fc | ||
|
|
8362829617 | ||
|
|
b5139e8af4 | ||
|
|
ed1bb5c5a4 | ||
|
|
a11c17fbf5 | ||
|
|
f6621e9bd8 | ||
|
|
1110501c7e | ||
|
|
de868f58b1 | ||
|
|
eb7b0c5b7e | ||
|
|
e6befab9ed | ||
|
|
6f4df926a2 | ||
|
|
be949a0174 | ||
|
|
7a24cecd93 | ||
|
|
bcde7b3699 | ||
|
|
52490771eb | ||
|
|
a3d17da32c | ||
|
|
f950595767 | ||
|
|
dfaa79d12a | ||
|
|
04d5f20b46 | ||
|
|
48086358d2 | ||
|
|
256a69a7d0 | ||
|
|
9ec8358609 | ||
|
|
4e82d6b89b | ||
|
|
6662a56be2 | ||
|
|
5696aad365 | ||
|
|
28f7c064f0 | ||
|
|
3b97ca1e47 | ||
|
|
026e97c4b7 | ||
|
|
d7b34d9f7f | ||
|
|
9cbb1b1336 | ||
|
|
c2944a648f | ||
|
|
ae4bf6c7ab | ||
|
|
b11c126afb | ||
|
|
dd4b3e4a72 | ||
|
|
096b76a350 | ||
|
|
8d4bd7adbb | ||
|
|
5cc20e8bad | ||
|
|
bd7536b6d3 | ||
|
|
c42013c0a3 | ||
|
|
482217c8f9 | ||
|
|
a6ca327118 | ||
|
|
e3ce273108 | ||
|
|
03dc0672b3 | ||
|
|
48cd2b83df | ||
|
|
79e067a8f5 | ||
|
|
5eddcf6f3d | ||
|
|
5e1291bc74 | ||
|
|
292a9da383 | ||
|
|
cb726856ae | ||
|
|
cd96bd0606 | ||
|
|
5913f30f3e | ||
|
|
c7eaa28731 | ||
|
|
2773c24e2a | ||
|
|
3323f2ae33 | ||
|
|
0ec7fa1b04 | ||
|
|
adccce07f4 |
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,10 +1,10 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
serra
|
|
||||||
!src/serra
|
|
||||||
_db/*
|
_db/*
|
||||||
|
/serra
|
||||||
!_db/.placeholder
|
!_db/.placeholder
|
||||||
_backup/*
|
_backup/*
|
||||||
!_backup/.placeholder
|
!_backup/.placeholder
|
||||||
dist/
|
dist/
|
||||||
add.log
|
add.log
|
||||||
.task/**
|
.task/**
|
||||||
|
.envrc
|
||||||
|
|||||||
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:
|
|
||||||
- replacements:
|
|
||||||
darwin: Darwin
|
|
||||||
linux: Linux
|
|
||||||
windows: Windows
|
|
||||||
openbsd: OpenBSD
|
|
||||||
386: i386
|
|
||||||
amd64: x86_64
|
|
||||||
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.17.6
|
golang 1.21.3
|
||||||
|
|||||||
26
Dockerfile
Normal file
26
Dockerfile
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
FROM golang:1.25-alpine AS build
|
||||||
|
|
||||||
|
RUN apk update && apk add --no-cache git ca-certificates curl
|
||||||
|
|
||||||
|
WORKDIR /go/src/app
|
||||||
|
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
|
||||||
|
|
||||||
|
# build
|
||||||
|
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=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
|
||||||
|
EXPOSE 8080
|
||||||
|
CMD [ "/go/src/app/serra", "web" ]
|
||||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2023 Florian Baumann
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@ -5,20 +5,22 @@ version: '3'
|
|||||||
tasks:
|
tasks:
|
||||||
build:
|
build:
|
||||||
cmds:
|
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:
|
sources:
|
||||||
- "src/serra/**/*.go"
|
- "pkg/serra/**/*.go"
|
||||||
- "serra.go"
|
- "cmd/serra/serra.go"
|
||||||
generates:
|
generates:
|
||||||
- "./serra"
|
- "./serra"
|
||||||
|
|
||||||
release:
|
release:
|
||||||
interactive: true
|
interactive: true
|
||||||
cmds:
|
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
|
- read -p "Version v1.1.1 " version ; git tag $version
|
||||||
- git push --tags
|
- git push --tags
|
||||||
- goreleaser release --rm-dist
|
- goreleaser release --clean
|
||||||
|
- docker build . -t ghcr.io/noqqe/serra:`git describe --tags`
|
||||||
|
- docker push ghcr.io/noqqe/serra:`git describe --tags`
|
||||||
|
|
||||||
default:
|
default:
|
||||||
cmds:
|
cmds:
|
||||||
|
|||||||
@ -1,27 +0,0 @@
|
|||||||
#!/usr/bin/env fish
|
|
||||||
|
|
||||||
function serra_add ()
|
|
||||||
|
|
||||||
# split input args
|
|
||||||
set -l args (string split " " $argv)
|
|
||||||
|
|
||||||
# check if user wants to exit
|
|
||||||
if [ "$args[2]" = "exit" ]
|
|
||||||
exit 0
|
|
||||||
end
|
|
||||||
|
|
||||||
# if usage contains a number at the end, add multiple ones
|
|
||||||
# if not, add a single one
|
|
||||||
if test -n "$args[3]"
|
|
||||||
serra add $args[1]/$args[2] --count=$args[3] -u
|
|
||||||
echo serra add $args[1]/$args[2] --count=$args[3] >> add.log
|
|
||||||
else
|
|
||||||
serra add $args[1]/$args[2] -u
|
|
||||||
echo serra add $args[1]/$args[2] >> add.log
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
while true;
|
|
||||||
read -l c
|
|
||||||
serra_add $argv[1] $c
|
|
||||||
end
|
|
||||||
@ -1,7 +1,7 @@
|
|||||||
// Package main provides a typing test
|
// Package main provides a typing test
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import "github.com/noqqe/serra/src/serra"
|
import "github.com/noqqe/serra/pkg/serra"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
serra.Execute()
|
serra.Execute()
|
||||||
51
demo.tape
51
demo.tape
@ -1,51 +0,0 @@
|
|||||||
# VHS documentation
|
|
||||||
#
|
|
||||||
# Output:
|
|
||||||
# Output <path>.gif Create a GIF output at the given <path>
|
|
||||||
# Output <path>.mp4 Create an MP4 output at the given <path>
|
|
||||||
# Output <path>.webm Create a WebM output at the given <path>
|
|
||||||
#
|
|
||||||
# Settings:
|
|
||||||
# Set FontSize <number> Set the font size of the terminal
|
|
||||||
# Set FontFamily <string> Set the font family of the terminal
|
|
||||||
# Set Height <number> Set the height of the terminal
|
|
||||||
# Set Width <number> Set the width of the terminal
|
|
||||||
# Set LetterSpacing <float> Set the font letter spacing (tracking)
|
|
||||||
# Set LineHeight <float> Set the font line height
|
|
||||||
# Set Theme <string> Set the theme of the terminal (JSON)
|
|
||||||
# Set Padding <number> Set the padding of the terminal
|
|
||||||
# Set Framerate <number> Set the framerate of the recording
|
|
||||||
# Set PlaybackSpeed <float> Set the playback speed of the recording
|
|
||||||
#
|
|
||||||
# Sleep:
|
|
||||||
# Sleep <time> Sleep for a set amount of <time> in seconds
|
|
||||||
#
|
|
||||||
# Type:
|
|
||||||
# Type[@<time>] "<characters>" Type <characters> into the terminal with a
|
|
||||||
# <time> delay between each character
|
|
||||||
#
|
|
||||||
# Keys:
|
|
||||||
# Backspace[@<time>] [number] Press the Backspace key
|
|
||||||
# Down[@<time>] [number] Press the Down key
|
|
||||||
# Enter[@<time>] [number] Press the Enter key
|
|
||||||
# Space[@<time>] [number] Press the Space key
|
|
||||||
# Tab[@<time>] [number] Press the Tab key
|
|
||||||
# Left[@<time>] [number] Press the Left Arrow key
|
|
||||||
# Right[@<time>] [number] Press the Right Arrow key
|
|
||||||
# Up[@<time>] [number] Press the Up Arrow key
|
|
||||||
# Down[@<time>] [number] Press the Down Arrow key
|
|
||||||
# Ctrl+<key> Press the Control key + <key> (e.g. Ctrl+C)
|
|
||||||
#
|
|
||||||
# Display:
|
|
||||||
# Hide Hide the subsequent commands from the output
|
|
||||||
# Show Show the subsequent commands in the output
|
|
||||||
|
|
||||||
Output demo.gif
|
|
||||||
|
|
||||||
Set FontSize 32
|
|
||||||
Set Width 1200
|
|
||||||
Set Height 600
|
|
||||||
|
|
||||||
Type "serra --version" Sleep 500ms Enter
|
|
||||||
|
|
||||||
Sleep 5s
|
|
||||||
@ -2,7 +2,7 @@ version: '3.6'
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
mongo:
|
mongo:
|
||||||
image: mongo
|
image: mongo:6
|
||||||
restart: always
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
- 27017:27017
|
- 27017:27017
|
||||||
|
|||||||
66
go.mod
66
go.mod
@ -1,9 +1,67 @@
|
|||||||
module github.com/noqqe/serra
|
module github.com/noqqe/serra
|
||||||
|
|
||||||
go 1.14
|
go 1.25.0
|
||||||
|
|
||||||
|
toolchain go1.25.4
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/schollz/progressbar/v3 v3.8.5
|
github.com/charmbracelet/log v0.4.0
|
||||||
github.com/spf13/cobra v1.3.0
|
github.com/chzyer/readline v1.5.1
|
||||||
go.mongodb.org/mongo-driver v1.8.1
|
github.com/gin-gonic/gin v1.10.0
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0
|
||||||
|
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
|
||||||
)
|
)
|
||||||
|
|||||||
912
go.sum
912
go.sum
@ -1,808 +1,180 @@
|
|||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
github.com/bytedance/sonic v1.12.3 h1:W2MGa7RCU1QTeYRTPE3+88mVC0yXmsRQRChiyVocVjU=
|
||||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
github.com/bytedance/sonic v1.12.3/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=
|
||||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
github.com/bytedance/sonic/loader v0.2.1 h1:1GgorWTqf12TA8mma4DDSbaQigE2wOgQo7iCjjJv3+E=
|
||||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
github.com/bytedance/sonic/loader v0.2.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
github.com/charmbracelet/lipgloss v0.13.0 h1:4X3PPeoWEDCMvzDvGmTajSyYPcZM4+y8sCA/SsA3cjw=
|
||||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
github.com/charmbracelet/lipgloss v0.13.0/go.mod h1:nw4zy0SBX/F/eAO1cWdcvy6qnkDUxr8Lw7dvFrAIbbY=
|
||||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM=
|
||||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM=
|
||||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
github.com/charmbracelet/x/ansi v0.3.2 h1:wsEwgAN+C9U06l9dCVMX0/L3x7ptvY1qmjMwyfE6USY=
|
||||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
github.com/charmbracelet/x/ansi v0.3.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
|
||||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
github.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM=
|
||||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0iwWCOK1q10rlY=
|
||||||
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
|
github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=
|
||||||
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
|
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
|
||||||
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
|
github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI=
|
||||||
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
|
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
|
||||||
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
|
github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
|
||||||
cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
|
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
|
||||||
cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
|
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||||
cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
|
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||||
cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
|
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||||
cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
|
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||||
cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
|
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
|
|
||||||
cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM=
|
|
||||||
cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
|
|
||||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
|
||||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
|
||||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
|
||||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
|
||||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
|
||||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
|
||||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
|
||||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
|
||||||
cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY=
|
|
||||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
|
||||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
|
||||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
|
||||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
|
||||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
|
||||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
|
||||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
|
||||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
|
||||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
|
||||||
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
|
||||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
|
||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
|
||||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
|
||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
|
||||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
|
||||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
|
||||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
|
||||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
|
||||||
github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=
|
|
||||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
|
||||||
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
|
||||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
|
||||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
|
||||||
github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
|
||||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
|
||||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
|
||||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
|
||||||
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
|
|
||||||
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
|
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
|
||||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
|
||||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
|
||||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
|
||||||
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
|
|
||||||
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
|
||||||
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
|
||||||
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
|
||||||
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
|
||||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
|
||||||
github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
|
||||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
|
||||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.1/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.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/dolmen-go/kittyimg v0.0.0-20250610224728-874967bd8ea4 h1:KGRb+vxMx5pGsfDjDSW2Th+b2OEflb0yC3s0daCmiYU=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/dolmen-go/kittyimg v0.0.0-20250610224728-874967bd8ea4/go.mod h1:2vk7ATPVcI7uW4Sh6PrSQvtO+Czmq8509xcg/y8Osd0=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
|
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
|
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||||
github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ=
|
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws=
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
|
||||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
|
||||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
|
||||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
|
||||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
|
||||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
|
||||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
|
||||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
|
||||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
|
||||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
|
||||||
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
|
||||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
|
||||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
|
||||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
|
||||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
|
||||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
|
||||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
|
||||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
|
||||||
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
|
|
||||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
|
||||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
|
||||||
github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
|
|
||||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
|
||||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.4/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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
|
|
||||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
|
||||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
|
||||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
|
||||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
|
||||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
|
||||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
|
||||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
|
||||||
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
|
||||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
|
||||||
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
|
||||||
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
|
||||||
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
|
||||||
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
|
||||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
|
||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
|
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
|
||||||
github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
|
|
||||||
github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
|
|
||||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
|
||||||
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
|
||||||
github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
|
||||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
|
||||||
github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
|
||||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
|
||||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
|
||||||
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
|
|
||||||
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
|
|
||||||
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
|
|
||||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
|
||||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
|
||||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
|
||||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
|
||||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
|
||||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
|
||||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
|
||||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
|
||||||
github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY=
|
|
||||||
github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
|
|
||||||
github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
|
|
||||||
github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
|
|
||||||
github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
|
|
||||||
github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
|
|
||||||
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
|
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
|
||||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
|
||||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
|
||||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
|
||||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
|
||||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
|
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
|
||||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||||
github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
|
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|
||||||
github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w=
|
|
||||||
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
|
||||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
|
||||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
|
||||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
|
||||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
|
||||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
|
||||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
|
||||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
|
||||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
|
||||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
|
||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
|
||||||
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
|
||||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
|
||||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
|
||||||
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
|
||||||
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
|
||||||
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
|
|
||||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
|
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/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
|
||||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
|
||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
|
||||||
github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
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/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
||||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
|
||||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
|
||||||
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
|
||||||
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
|
|
||||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
|
||||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
|
||||||
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
|
|
||||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
|
||||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
|
||||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
|
||||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
|
||||||
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
|
|
||||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
|
||||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
|
||||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
|
||||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
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/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
github.com/schollz/progressbar/v3 v3.16.1 h1:RnF1neWZFzLCoGx8yp1yF7SDl4AzNDI5y4I0aUJRrZQ=
|
||||||
github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig=
|
github.com/schollz/progressbar/v3 v3.16.1/go.mod h1:I2ILR76gz5VXqYMIY/LdLecvMHDPVcQm3W/MSKi1TME=
|
||||||
github.com/schollz/progressbar/v3 v3.8.5 h1:VcmmNRO+eFN3B0m5dta6FXYXY+MEJmXdWoIS+jjssQM=
|
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||||
github.com/schollz/progressbar/v3 v3.8.5/go.mod h1:ewO25kD7ZlaJFTvMeOItkOZa8kXu1UvFs379htE8HMQ=
|
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
|
||||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
|
||||||
github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
|
|
||||||
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
|
||||||
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
|
||||||
github.com/spf13/cobra v1.3.0 h1:R7cSvGu+Vv+qX0gW5R/85dx2kmmJT5z5NM8ifdYjdn0=
|
|
||||||
github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4=
|
|
||||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
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/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
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.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 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
||||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||||
github.com/xdg-go/scram v1.0.2 h1:akYIkZ28e6A96dkWNJQu3nmCzH3YfwMPQExUYDaRv7w=
|
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
|
||||||
github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
|
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
|
||||||
github.com/xdg-go/stringprep v1.0.2 h1:6iq84/ryjjeRmMJwxutI51F2GIPlP5BfTvXHeYjyhBc=
|
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
|
||||||
github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
|
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
||||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=
|
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
|
||||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
|
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
|
||||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
go.mongodb.org/mongo-driver v1.17.1 h1:Wic5cJIwJgSpBhe3lx3+/RybR5PiYRMpVFgO7cOHyIM=
|
||||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
go.mongodb.org/mongo-driver v1.17.1/go.mod h1:wwWm/+BuOddhcq3n68LKRmgk2wXzmF6s0SFOa0GINL4=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
golang.org/x/arch v0.11.0 h1:KXV8WWKCXm6tRpLirl2szsO5j/oOODwZf4hATmGVNs4=
|
||||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
golang.org/x/arch v0.11.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||||
go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
|
|
||||||
go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
|
||||||
go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs=
|
|
||||||
go.mongodb.org/mongo-driver v1.8.1 h1:OZE4Wni/SJlrcmSIBRYNzunX5TKxjrTS4jKSnA99oKU=
|
|
||||||
go.mongodb.org/mongo-driver v1.8.1/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY=
|
|
||||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
|
||||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
|
||||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
|
||||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
|
||||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
|
||||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
|
||||||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
|
||||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
|
||||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
|
||||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
|
||||||
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
|
|
||||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
|
||||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
||||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
||||||
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
|
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
|
||||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
|
||||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M=
|
|
||||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
|
||||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
|
||||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
|
||||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
|
||||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
|
||||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
|
||||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
|
||||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
|
||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
|
||||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
|
||||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
|
||||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
|
||||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
|
||||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
|
||||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
|
||||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
|
||||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
|
||||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
|
||||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
|
||||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
||||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
||||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
||||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
||||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
||||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
|
||||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
|
||||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
|
||||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
|
||||||
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
|
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
|
||||||
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
|
||||||
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/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.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/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-20210630005230-0f9fa26af87c/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-20210806184541-e5e7981a1069/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-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||||
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
|
|
||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
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-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
|
||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
|
||||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
|
||||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
|
||||||
golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
|
||||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
|
||||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
|
||||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
|
||||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/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.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
|
||||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
|
||||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
|
||||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
|
||||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
|
||||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
|
||||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
|
||||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
|
||||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
|
||||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
|
||||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
|
||||||
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
|
||||||
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
|
||||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
|
||||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
|
||||||
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
|
||||||
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
|
||||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
|
||||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
|
||||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
|
||||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
|
||||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
|
||||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
|
||||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
|
||||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
|
||||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
|
||||||
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
|
||||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
|
||||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
|
||||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
|
||||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
|
||||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
|
||||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
|
||||||
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
|
||||||
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
|
||||||
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
|
||||||
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
|
|
||||||
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
|
|
||||||
google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
|
|
||||||
google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
|
|
||||||
google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
|
|
||||||
google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
|
|
||||||
google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
|
|
||||||
google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
|
|
||||||
google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
|
|
||||||
google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
|
|
||||||
google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU=
|
|
||||||
google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
|
|
||||||
google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw=
|
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
|
||||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
|
||||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
|
||||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
|
||||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
|
||||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
|
||||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
|
||||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
|
||||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
|
||||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
|
||||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
|
||||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
|
||||||
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
|
|
||||||
google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
|
|
||||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
|
||||||
google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
|
||||||
google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
|
||||||
google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
|
|
||||||
google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
|
|
||||||
google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
|
|
||||||
google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
|
|
||||||
google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
|
|
||||||
google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
|
|
||||||
google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
|
||||||
google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
|
||||||
google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
|
||||||
google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
|
||||||
google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
|
||||||
google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
|
||||||
google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
|
||||||
google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
|
||||||
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
|
||||||
google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
|
||||||
google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
|
||||||
google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
|
||||||
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
|
||||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
|
||||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
|
||||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
|
||||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
|
||||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
|
||||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
|
||||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
|
||||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
|
||||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
|
||||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
|
||||||
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
|
||||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
|
||||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
|
||||||
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
|
||||||
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
|
||||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
|
||||||
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
|
||||||
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
|
||||||
google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
|
||||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
|
||||||
google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
|
|
||||||
google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
|
|
||||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
|
||||||
google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
|
||||||
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
|
|
||||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
|
||||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
|
||||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
|
||||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
|
||||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
|
||||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
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-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
|
||||||
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
|
||||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
|
||||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
|
||||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
|
||||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
|
||||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
|
||||||
|
|||||||
BIN
imgs/check.png
Normal file
BIN
imgs/check.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
BIN
imgs/logo.png
Normal file
BIN
imgs/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 720 KiB |
38
mongodb_queries.md
Normal file
38
mongodb_queries.md
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
|
||||||
|
## Cheatsheet Queries
|
||||||
|
|
||||||
|
Find cards that increased prices
|
||||||
|
|
||||||
|
db.cards.find({$expr: {$gt: [{$arrayElemAt: ["$serra_prices", -2]}, {$arrayElemAt: ["$serra_prices", -1]}]}}, {name:1})
|
||||||
|
|
||||||
|
Update card Price
|
||||||
|
|
||||||
|
db.cards.update(
|
||||||
|
{'_id':'8fa2ecf9-b53c-4f1d-9028-ca3820d043cb'},
|
||||||
|
{$set:{'serra_updated':ISODate("2021-11-02T09:28:56.504Z")},
|
||||||
|
$push: {"serra_prices": { date: ISODate("2021-11-02T09:28:56.504Z"), value: 0.1 }}});
|
||||||
|
|
||||||
|
Set value
|
||||||
|
|
||||||
|
db.cards.aggregate([{ $group: { _id: { set: "$set" }, value: { $sum: { $multiply: ["$prices.eur", "$serra_count"] } }, count: { $sum: 1 } } }])
|
||||||
|
|
||||||
|
Color distribution
|
||||||
|
|
||||||
|
db.cards.aggregate([{ $group: { _id: { color: "$colors" }, count: { $sum: 1 } } }])
|
||||||
|
|
||||||
|
Calculate value of all sets
|
||||||
|
|
||||||
|
db.sets.aggregate({$match: {serra_prices: {$exists: true}}}, {$project: {name: 1, "totalValue": {$arrayElemAt: ["$serra_prices", -1]} }}, {$group: {_id: null, total: {$sum: "$totalValue.value" }}})
|
||||||
|
|
||||||
|
Calculate what cards gained most value in percent
|
||||||
|
|
||||||
|
db.cards.aggregate({$project: {set: 1, collectornumber:1, name: 1, "old": {$arrayElemAt: ["$serra_prices.value", -2]}, "current": {$arrayElemAt: ["$serra_prices.value", -1]} }}, {$match: {old: {$gt: 2}}} ,{$project: {name: 1,set:1,collectornumber:1,current:1, "rate": {$subtract: [{$divide: ["$current", {$divide: ["$old", 100]}]}, 100]} }}, {$sort: { rate: -1}})
|
||||||
|
|
||||||
|
Show when cards where added per month of the year
|
||||||
|
|
||||||
|
db.cards.aggregate({ $project: { month: { $month: "$serra_created" }, year: { $year: "$serra_created" }, name: 1 } }, { $group: { _id: { month: "$month", year: "$year" }, count: { $sum: 1 } } })
|
||||||
|
|
||||||
|
Show card count by artists
|
||||||
|
|
||||||
|
db.cards.aggregate({$group: { _id : "$artist", total : {$sum:1}}}, {$sort: {total:-1}})
|
||||||
|
|
||||||
190
pkg/serra/add.go
Normal file
190
pkg/serra/add.go
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
package serra
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/chzyer/readline"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
addCmd.Flags().Int64VarP(&count, "count", "c", 1, "Amount of cards to add")
|
||||||
|
addCmd.Flags().BoolVarP(&unique, "unique", "u", false, "Only add card if not existent yet")
|
||||||
|
addCmd.Flags().BoolVarP(&interactive, "interactive", "i", false, "Spin up interactive terminal")
|
||||||
|
addCmd.Flags().StringVarP(&set, "set", "s", "", "Filter by set code (usg/mmq/vow)")
|
||||||
|
addCmd.Flags().BoolVarP(&foil, "foil", "f", false, "Add foil variant of card")
|
||||||
|
rootCmd.AddCommand(addCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
var addCmd = &cobra.Command{
|
||||||
|
Aliases: []string{"a"},
|
||||||
|
Use: "add",
|
||||||
|
Short: "Add a card to your collection",
|
||||||
|
Long: "Adds a card from scryfall to your collection. Amount can be modified using flags",
|
||||||
|
SilenceErrors: true,
|
||||||
|
RunE: func(cmd *cobra.Command, cards []string) error {
|
||||||
|
if interactive {
|
||||||
|
addCardsInteractive(unique, set)
|
||||||
|
} else {
|
||||||
|
addCards(cards, unique, count)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func addCardsInteractive(unique bool, set string) {
|
||||||
|
l := Logger()
|
||||||
|
if len(set) == 0 {
|
||||||
|
l.Fatal("Option --set <set> must be given in interactive mode")
|
||||||
|
}
|
||||||
|
|
||||||
|
rl, err := readline.New(fmt.Sprintf("%s> ", set))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer rl.Close()
|
||||||
|
|
||||||
|
for {
|
||||||
|
line, err := rl.Readline()
|
||||||
|
if err != nil { // io.EOF
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// default is no foil
|
||||||
|
foil = false
|
||||||
|
|
||||||
|
// default is count 1
|
||||||
|
count = 1
|
||||||
|
|
||||||
|
// construct card input for addCards
|
||||||
|
card := []string{}
|
||||||
|
|
||||||
|
// Detect if input contains a dash, if it does it means the user wants to add a range of cards
|
||||||
|
if strings.Contains(line, "-") {
|
||||||
|
// Split input into two parts
|
||||||
|
parts := strings.Split(line, "-")
|
||||||
|
// Check if both parts are numbers
|
||||||
|
if _, err := strconv.Atoi(parts[0]); err == nil {
|
||||||
|
if _, err = strconv.Atoi(parts[1]); err == nil {
|
||||||
|
// Loop over range and add each card to card slice
|
||||||
|
start, _ := strconv.Atoi(parts[0])
|
||||||
|
end, _ := strconv.Atoi(parts[1])
|
||||||
|
for i := start; i <= end; i++ {
|
||||||
|
card = append(card, fmt.Sprintf("%s/%d", set, i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
|
||||||
|
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 {
|
||||||
|
c := co[0]
|
||||||
|
outputColor := coloredValue(c.getValue(foil))
|
||||||
|
|
||||||
|
if unique {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
if askConfirmation(&c) {
|
||||||
|
modifyCardCount(coll, &c, count, foil)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Fetch card from scryfall
|
||||||
|
c, err := fetchCard(setName, collectorNumber)
|
||||||
|
|
||||||
|
outputColor := coloredValue(c.getValue(foil))
|
||||||
|
if err != nil {
|
||||||
|
l.Warn(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write card to mongodb
|
||||||
|
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 {
|
||||||
|
l.Warn(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Give feedback of successfully added card
|
||||||
|
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)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
243
pkg/serra/card.go
Normal file
243
pkg/serra/card.go
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
var cardCmd = &cobra.Command{
|
||||||
|
Aliases: []string{"cards"},
|
||||||
|
Use: "card [card]",
|
||||||
|
Short: "Search & show cards from your collection",
|
||||||
|
Long: `Search and show cards from your collection.
|
||||||
|
If you directly put a card as an argument, it will be displayed
|
||||||
|
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, omitInDeck)
|
||||||
|
showCardList(cardList, detail)
|
||||||
|
} else {
|
||||||
|
ShowCard(cards)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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] == "" {
|
||||||
|
l.Warnf("Invalid card %s", v)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
cards, _ := coll.storageFind(bson.D{{"set", strings.Split(v, "/")[0]}, {"collectornumber", strings.Split(v, "/")[1]}}, bson.D{{"name", 1}}, 0, 0)
|
||||||
|
|
||||||
|
for _, card := range cards {
|
||||||
|
showCardDetails(&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)
|
||||||
|
|
||||||
|
filter := bson.D{}
|
||||||
|
|
||||||
|
switch rarity {
|
||||||
|
case "uncommon":
|
||||||
|
filter = append(filter, bson.E{"rarity", "uncommon"})
|
||||||
|
case "common":
|
||||||
|
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 {
|
||||||
|
sortStage = bson.D{{"prices.eur", 1}}
|
||||||
|
} else {
|
||||||
|
sortStage = bson.D{{"prices.usd", 1}}
|
||||||
|
}
|
||||||
|
case "number":
|
||||||
|
sortStage = bson.D{{"collectornumber", 1}}
|
||||||
|
case "name":
|
||||||
|
sortStage = bson.D{{"name", 1}}
|
||||||
|
case "added":
|
||||||
|
sortStage = bson.D{{"serra_created", 1}}
|
||||||
|
default:
|
||||||
|
sortStage = bson.D{{"name", 1}}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(set) > 0 {
|
||||||
|
filter = append(filter, bson.E{"set", set})
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(name) > 0 {
|
||||||
|
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"}}})
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cardType) > 0 {
|
||||||
|
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})
|
||||||
|
}
|
||||||
|
|
||||||
|
if foil {
|
||||||
|
filter = append(filter, bson.E{"serra_count_foil", bson.D{{"$gt", 0}}})
|
||||||
|
}
|
||||||
|
|
||||||
|
if omitInDeck {
|
||||||
|
filter = append(filter, bson.E{ "$expr", bson.D{{"$or",bson.A{
|
||||||
|
bson.D{{"$ne", bson.A{"$serra_count", "$serra_count_deck"}}},
|
||||||
|
bson.D{{"$ne", bson.A{"$serra_count_foil", "$serra_count_foil_deck"}}},
|
||||||
|
}}}})
|
||||||
|
}
|
||||||
|
|
||||||
|
cards, _ := coll.storageFind(filter, sortStage, skip, limit)
|
||||||
|
|
||||||
|
// This is needed because collectornumbers are strings (ie. "23a") but still we
|
||||||
|
// want it to be sorted numerically ... 1,2,3,10,11,100.
|
||||||
|
if sortby == "number" {
|
||||||
|
sort.Slice(cards, func(i, j int) bool {
|
||||||
|
return filterForDigits(cards[i].CollectorNumber) < filterForDigits(cards[j].CollectorNumber)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 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 (%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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !drawImg {
|
||||||
|
fmt.Printf("\nTotal Value: %s%.2f%s%s\n", Yellow, total, getCurrency(), Reset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func showCardDetails(card *Card) error {
|
||||||
|
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%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()
|
||||||
|
}
|
||||||
77
pkg/serra/check.go
Normal file
77
pkg/serra/check.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
package serra
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
checkCmd.Flags().StringVarP(&set, "set", "s", "", "Filter by set code (usg/mmq/vow)")
|
||||||
|
checkCmd.Flags().BoolVarP(&detail, "detail", "d", false, "Show details for cards (url)")
|
||||||
|
rootCmd.AddCommand(checkCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
var checkCmd = &cobra.Command{
|
||||||
|
Aliases: []string{"c"},
|
||||||
|
Use: "check",
|
||||||
|
Short: "Check if a card is in your collection",
|
||||||
|
Long: "Check if a card is in your collection. Useful for list comparsions",
|
||||||
|
SilenceErrors: true,
|
||||||
|
RunE: func(cmd *cobra.Command, cards []string) error {
|
||||||
|
checkCards(cards, detail)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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.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 {
|
||||||
|
l.Warn(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// If Card is in collection, print yes.
|
||||||
|
if len(co) >= 1 {
|
||||||
|
c := co[0]
|
||||||
|
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)
|
||||||
|
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
|
||||||
|
fmt.Printf("MISSING \"%s\"\n", card)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
storageDisconnect(client)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
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
|
||||||
|
}
|
||||||
33
pkg/serra/env.go
Normal file
33
pkg/serra/env.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package serra
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
const EUR = "€"
|
||||||
|
const USD = "$"
|
||||||
|
|
||||||
|
func getMongoDBURI() string {
|
||||||
|
l := Logger()
|
||||||
|
uri := os.Getenv("MONGODB_URI")
|
||||||
|
if uri == "" {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 EUR
|
||||||
|
case "USD":
|
||||||
|
return USD
|
||||||
|
default:
|
||||||
|
l.Warn("You did not configure SERRA_CURRENCY. Assuming \"USD\"")
|
||||||
|
return "$"
|
||||||
|
}
|
||||||
|
}
|
||||||
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))
|
||||||
|
}
|
||||||
@ -2,7 +2,6 @@ package serra
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"go.mongodb.org/mongo-driver/bson"
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
@ -13,9 +12,11 @@ func init() {
|
|||||||
rootCmd.AddCommand(topsCmd)
|
rootCmd.AddCommand(topsCmd)
|
||||||
rootCmd.AddCommand(flopsCmd)
|
rootCmd.AddCommand(flopsCmd)
|
||||||
topsCmd.Flags().Float64VarP(&limit, "limit", "l", 0, "Minimum card price to be shown in analysis")
|
topsCmd.Flags().Float64VarP(&limit, "limit", "l", 0, "Minimum card price to be shown in analysis")
|
||||||
topsCmd.Flags().StringVarP(&since, "since", "s", "0", "Since when should the gains be calculated")
|
topsCmd.Flags().BoolVarP(&sinceLastUpdate, "since-last-update", "u", false, "Show gains since last update")
|
||||||
|
topsCmd.Flags().BoolVarP(&sinceBeginning, "since-beginning", "b", true, "Show gains since beginning of records")
|
||||||
flopsCmd.Flags().Float64VarP(&limit, "limit", "l", 0, "Minimum card price to be shown in analysis")
|
flopsCmd.Flags().Float64VarP(&limit, "limit", "l", 0, "Minimum card price to be shown in analysis")
|
||||||
flopsCmd.Flags().StringVarP(&since, "since", "s", "0", "Since when should the losses be calculated")
|
flopsCmd.Flags().BoolVarP(&sinceLastUpdate, "since-last-update", "u", false, "Show losses since last update")
|
||||||
|
flopsCmd.Flags().BoolVarP(&sinceBeginning, "since-beginning", "b", true, "Show losses since beginning of records")
|
||||||
}
|
}
|
||||||
|
|
||||||
var topsCmd = &cobra.Command{
|
var topsCmd = &cobra.Command{
|
||||||
@ -24,7 +25,7 @@ var topsCmd = &cobra.Command{
|
|||||||
Short: "What cards gained most value",
|
Short: "What cards gained most value",
|
||||||
SilenceErrors: true,
|
SilenceErrors: true,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
Gains(limit, -1, since)
|
Gains(limit, -1)
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -35,21 +36,32 @@ var flopsCmd = &cobra.Command{
|
|||||||
Short: "What cards lost most value",
|
Short: "What cards lost most value",
|
||||||
SilenceErrors: true,
|
SilenceErrors: true,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
Gains(limit, 1, since)
|
Gains(limit, 1)
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func Gains(limit float64, sort int, since string) error {
|
func Gains(limit float64, sort int) error {
|
||||||
|
|
||||||
client := storage_connect()
|
client := storageConnect()
|
||||||
coll := &Collection{client.Database("serra").Collection("cards")}
|
coll := &Collection{client.Database("serra").Collection("cards")}
|
||||||
setcoll := &Collection{client.Database("serra").Collection("sets")}
|
setcoll := &Collection{client.Database("serra").Collection("sets")}
|
||||||
defer storage_disconnect(client)
|
defer storageDisconnect(client)
|
||||||
|
|
||||||
old, _ := strconv.Atoi(since)
|
var old int
|
||||||
|
if sinceBeginning {
|
||||||
|
old = 0
|
||||||
|
}
|
||||||
|
if sinceLastUpdate {
|
||||||
|
old = -2
|
||||||
|
}
|
||||||
|
|
||||||
raise_pipeline := mongo.Pipeline{
|
currencyField := "$serra_prices.usd"
|
||||||
|
if getCurrency() == EUR {
|
||||||
|
currencyField = "$serra_prices.eur"
|
||||||
|
}
|
||||||
|
|
||||||
|
raisePipeline := mongo.Pipeline{
|
||||||
bson.D{{"$project",
|
bson.D{{"$project",
|
||||||
bson.D{
|
bson.D{
|
||||||
{"name", true},
|
{"name", true},
|
||||||
@ -57,12 +69,12 @@ func Gains(limit float64, sort int, since string) error {
|
|||||||
{"collectornumber", true},
|
{"collectornumber", true},
|
||||||
{"old",
|
{"old",
|
||||||
bson.D{{"$arrayElemAt",
|
bson.D{{"$arrayElemAt",
|
||||||
bson.A{"$serra_prices.value", old},
|
bson.A{currencyField, old},
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
{"current",
|
{"current",
|
||||||
bson.D{{"$arrayElemAt",
|
bson.D{{"$arrayElemAt",
|
||||||
bson.A{"$serra_prices.value", -1},
|
bson.A{currencyField, -1},
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -97,21 +109,21 @@ func Gains(limit float64, sort int, since string) error {
|
|||||||
bson.D{{"rate", sort}}}},
|
bson.D{{"rate", sort}}}},
|
||||||
bson.D{{"$limit", 20}},
|
bson.D{{"$limit", 20}},
|
||||||
}
|
}
|
||||||
raise, _ := coll.storage_aggregate(raise_pipeline)
|
raise, _ := coll.storageAggregate(raisePipeline)
|
||||||
|
|
||||||
sraise_pipeline := mongo.Pipeline{
|
sraisePipeline := mongo.Pipeline{
|
||||||
bson.D{{"$project",
|
bson.D{{"$project",
|
||||||
bson.D{
|
bson.D{
|
||||||
{"name", true},
|
{"name", true},
|
||||||
{"code", true},
|
{"code", true},
|
||||||
{"old",
|
{"old",
|
||||||
bson.D{{"$arrayElemAt",
|
bson.D{{"$arrayElemAt",
|
||||||
bson.A{"$serra_prices.value", old},
|
bson.A{currencyField, old},
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
{"current",
|
{"current",
|
||||||
bson.D{{"$arrayElemAt",
|
bson.D{{"$arrayElemAt",
|
||||||
bson.A{"$serra_prices.value", -1},
|
bson.A{currencyField, -1},
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -145,25 +157,25 @@ func Gains(limit float64, sort int, since string) error {
|
|||||||
bson.D{{"rate", sort}}}},
|
bson.D{{"rate", sort}}}},
|
||||||
bson.D{{"$limit", 10}},
|
bson.D{{"$limit", 10}},
|
||||||
}
|
}
|
||||||
sraise, _ := setcoll.storage_aggregate(sraise_pipeline)
|
sraise, _ := setcoll.storageAggregate(sraisePipeline)
|
||||||
|
|
||||||
// percentage coloring
|
// percentage coloring
|
||||||
var p_color string
|
var pColor string
|
||||||
if sort == 1 {
|
if sort == 1 {
|
||||||
p_color = Red
|
pColor = Red
|
||||||
} else {
|
} else {
|
||||||
p_color = Green
|
pColor = Green
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%sCards%s\n", Purple, Reset)
|
fmt.Printf("%sCards%s\n", Purple, Reset)
|
||||||
// print each card
|
// print each card
|
||||||
for _, e := range raise {
|
for _, e := range raise {
|
||||||
fmt.Printf("%s%+.0f%%%s %s %s(%s/%s)%s (%.2f->%s%.2f EUR%s) \n", p_color, e["rate"], Reset, e["name"], Yellow, e["set"], e["collectornumber"], Reset, e["old"], Green, e["current"], Reset)
|
fmt.Printf("%s%+.0f%%%s %s %s(%s/%s)%s (%.2f->%s%.2f%s%s) \n", pColor, e["rate"], Reset, e["name"], Yellow, e["set"], e["collectornumber"], Reset, e["old"], Green, e["current"], getCurrency(), Reset)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("\n%sSets%s\n", Purple, Reset)
|
fmt.Printf("\n%sSets%s\n", Purple, Reset)
|
||||||
for _, e := range sraise {
|
for _, e := range sraise {
|
||||||
fmt.Printf("%s%+.0f%%%s %s %s(%s)%s (%.2f->%s%.2f EUR%s) \n", p_color, e["rate"], Reset, e["name"], Yellow, e["code"], Reset, e["old"], Green, e["current"], Reset)
|
fmt.Printf("%s%+.0f%%%s %s %s(%s)%s (%.2f->%s%.2f%s%s) \n", pColor, e["rate"], Reset, e["name"], Yellow, e["code"], Reset, e["old"], Green, e["current"], getCurrency(), Reset)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
323
pkg/serra/helpers.go
Normal file
323
pkg/serra/helpers.go
Normal file
@ -0,0 +1,323 @@
|
|||||||
|
package serra
|
||||||
|
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Rarities struct {
|
||||||
|
Rares, Uncommons, Commons, Mythics float64
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// update card amount
|
||||||
|
var update bson.M
|
||||||
|
if foil {
|
||||||
|
update = bson.M{
|
||||||
|
"$set": bson.M{"serra_count_foil": storedCard.SerraCountFoil + amount},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
update = bson.M{
|
||||||
|
"$set": bson.M{"serra_count": storedCard.SerraCount + amount},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
coll.storageUpdate(bson.M{"_id": bson.M{"$eq": c.ID}}, update)
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func modifyCardDeckCount(coll *Collection, c *Card, amount int64, foil bool) error {
|
||||||
|
l := Logger()
|
||||||
|
|
||||||
|
storedCard, err := getStoredCard(coll, c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// update card amount
|
||||||
|
var update bson.M
|
||||||
|
if foil {
|
||||||
|
newAmount := min(max(storedCard.SerraCountFoilDeck + amount, 0), storedCard.SerraCountFoil)
|
||||||
|
l.Infof("%d / %d available", storedCard.SerraCountFoil - newAmount, storedCard.SerraCountFoil)
|
||||||
|
update = bson.M{
|
||||||
|
"$set": bson.M{"serra_count_foil_deck": newAmount},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newAmount := min(max(storedCard.SerraCountDeck + amount, 0), storedCard.SerraCount)
|
||||||
|
l.Infof("%d / %d available", storedCard.SerraCount - newAmount, storedCard.SerraCount)
|
||||||
|
update = bson.M{
|
||||||
|
"$set": bson.M{"serra_count_deck": newAmount},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
coll.storageUpdate(bson.M{"_id": bson.M{"$eq": c.ID}}, update)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func findCardByCollectorNumber(coll *Collection, setCode string, collectorNumber string) (*Card, error) {
|
||||||
|
sort := bson.D{{"_id", 1}}
|
||||||
|
searchFilter := bson.D{{"set", setCode}, {"collectornumber", collectorNumber}}
|
||||||
|
storedCards, err := coll.storageFind(searchFilter, sort, 0, 0)
|
||||||
|
if err != nil {
|
||||||
|
return &Card{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(storedCards) < 1 {
|
||||||
|
return &Card{}, errors.New("Card not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &storedCards[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringToTime(s primitive.DateTime) string {
|
||||||
|
return time.UnixMilli(int64(s)).Format("2006-01-02")
|
||||||
|
}
|
||||||
|
|
||||||
|
// missing compares two slices and returns slice of differences
|
||||||
|
func missing(a, b []string) []string {
|
||||||
|
type void struct{}
|
||||||
|
// create map with length of the 'a' slice
|
||||||
|
ma := make(map[string]void, len(a))
|
||||||
|
diffs := []string{}
|
||||||
|
// Convert first slice to map with empty struct (0 bytes)
|
||||||
|
for _, ka := range a {
|
||||||
|
ma[ka] = void{}
|
||||||
|
}
|
||||||
|
// find missing values in a
|
||||||
|
for _, kb := range b {
|
||||||
|
if _, ok := ma[kb]; !ok {
|
||||||
|
diffs = append(diffs, kb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return diffs
|
||||||
|
}
|
||||||
|
|
||||||
|
func findSetByCode(coll *Collection, setcode string) (*Set, error) {
|
||||||
|
storedSets, err := coll.storageFindSet(bson.D{{"code", setcode}}, bson.D{{"_id", 1}})
|
||||||
|
if err != nil {
|
||||||
|
return &Set{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(storedSets) < 1 {
|
||||||
|
return &Set{}, errors.New("Set not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &storedSets[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertManaSymbols(sym []interface{}) string {
|
||||||
|
var mana string
|
||||||
|
|
||||||
|
if len(sym) == 0 {
|
||||||
|
mana = mana + "None" //probibited sign for lands
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range sym {
|
||||||
|
switch v {
|
||||||
|
case "B":
|
||||||
|
mana = mana + "Black" //black
|
||||||
|
case "R":
|
||||||
|
mana = mana + "Red" //red
|
||||||
|
case "G":
|
||||||
|
mana = mana + "Green" //green
|
||||||
|
case "U":
|
||||||
|
mana = mana + "Blue" //blue
|
||||||
|
case "W":
|
||||||
|
mana = mana + "White" //white
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mana
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertRarities(rar []primitive.M) Rarities {
|
||||||
|
|
||||||
|
// this is maybe the ugliest way someone could choose to verify, if a rarity type is missing
|
||||||
|
// [
|
||||||
|
// { _id: { rarity: 'common' }, count: 20 },
|
||||||
|
// { _id: { rarity: 'uncommon' }, count: 2 }
|
||||||
|
// ]
|
||||||
|
// if a result like this is there, 1 rarity type "rare" is not in the array. and needs to be
|
||||||
|
// initialized with 0, otherwise we get a panic
|
||||||
|
|
||||||
|
var ri Rarities
|
||||||
|
for _, r := range rar {
|
||||||
|
switch r["_id"] {
|
||||||
|
case "rare":
|
||||||
|
ri.Rares = r["count"].(float64)
|
||||||
|
case "uncommon":
|
||||||
|
ri.Uncommons = r["count"].(float64)
|
||||||
|
case "common":
|
||||||
|
ri.Commons = r["count"].(float64)
|
||||||
|
case "mythic":
|
||||||
|
ri.Mythics = r["count"].(float64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ri
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func showPriceHistory(prices []PriceEntry, prefix string, total bool) {
|
||||||
|
|
||||||
|
var before float64
|
||||||
|
for _, e := range prices {
|
||||||
|
|
||||||
|
var value float64
|
||||||
|
if total {
|
||||||
|
if getCurrency() == EUR {
|
||||||
|
value = e.Eur + e.EurFoil
|
||||||
|
} else {
|
||||||
|
value = e.Usd + e.UsdFoil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if getCurrency() == EUR {
|
||||||
|
value = e.Eur
|
||||||
|
} else {
|
||||||
|
value = e.Usd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if value > before && before != 0 {
|
||||||
|
fmt.Printf("%s%s%s %.2f%s%s (%+.2f%%, %+.2f%s)\n", prefix, stringToTime(e.Date), Green, value, getCurrency(), Reset, (value/before*100)-100, value-before, getCurrency())
|
||||||
|
} else if value < before {
|
||||||
|
fmt.Printf("%s%s%s %.2f%s%s (%+.2f%%, %+.2f%s)\n", prefix, stringToTime(e.Date), Red, value, getCurrency(), Reset, (value/before*100)-100, value-before, getCurrency())
|
||||||
|
} else {
|
||||||
|
fmt.Printf("%s%s %.2f%s%s\n", prefix, stringToTime(e.Date), value, getCurrency(), Reset)
|
||||||
|
}
|
||||||
|
before = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterForDigits(str string) int {
|
||||||
|
var numStr string
|
||||||
|
for _, c := range str {
|
||||||
|
if unicode.IsDigit(c) {
|
||||||
|
numStr += string(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s, _ := strconv.Atoi(numStr)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFloat64(unknown interface{}) (float64, error) {
|
||||||
|
switch i := unknown.(type) {
|
||||||
|
case float64:
|
||||||
|
return i, nil
|
||||||
|
case float32:
|
||||||
|
return float64(i), nil
|
||||||
|
case int64:
|
||||||
|
return float64(i), nil
|
||||||
|
case int32:
|
||||||
|
return float64(i), nil
|
||||||
|
case int:
|
||||||
|
return float64(i), nil
|
||||||
|
case uint64:
|
||||||
|
return float64(i), nil
|
||||||
|
case uint32:
|
||||||
|
return float64(i), nil
|
||||||
|
case uint:
|
||||||
|
return float64(i), nil
|
||||||
|
default:
|
||||||
|
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'
|
||||||
|
}
|
||||||
84
pkg/serra/missing.go
Normal file
84
pkg/serra/missing.go
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
package serra
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(missingCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
var missingCmd = &cobra.Command{
|
||||||
|
Aliases: []string{"m"},
|
||||||
|
Use: "missing <set>",
|
||||||
|
Short: "Display missing cards from a set",
|
||||||
|
Long: `In case you are a set collector, you can generate a list of
|
||||||
|
cards you dont own (yet) :)`,
|
||||||
|
SilenceErrors: true,
|
||||||
|
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 {
|
||||||
|
l.Errorf("Set %s not found or no card in your collection.", setName[0])
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch set informations
|
||||||
|
setcoll := &Collection{client.Database("serra").Collection("sets")}
|
||||||
|
sets, _ := setcoll.storageFindSet(bson.D{{"code", setName[0]}}, bson.D{{"_id", 1}})
|
||||||
|
set := sets[0]
|
||||||
|
|
||||||
|
fmt.Printf("Missing cards in %s\n", sets[0].Name)
|
||||||
|
|
||||||
|
// generate set with all setnumbers
|
||||||
|
var (
|
||||||
|
completeSet []string
|
||||||
|
i int64
|
||||||
|
)
|
||||||
|
for i = 1; i <= set.CardCount; i++ {
|
||||||
|
completeSet = append(completeSet, strconv.FormatInt(i, 10))
|
||||||
|
}
|
||||||
|
|
||||||
|
// iterate over all cards in collection
|
||||||
|
var inCollection []string
|
||||||
|
for _, c := range cards {
|
||||||
|
inCollection = append(inCollection, c.CollectorNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
misses := missing(inCollection, completeSet)
|
||||||
|
|
||||||
|
// Fetch all missing cards
|
||||||
|
missingCards := []*Card{}
|
||||||
|
for _, m := range misses {
|
||||||
|
card, err := fetchCard(setName[0], m)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
missingCards = append(missingCards, card)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort the missing cards by ID
|
||||||
|
sort.Slice(missingCards, func(i, j int) bool {
|
||||||
|
id1, _ := strconv.Atoi(missingCards[i].CollectorNumber)
|
||||||
|
id2, _ := strconv.Atoi(missingCards[j].CollectorNumber)
|
||||||
|
return id1 < id2
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, card := range missingCards {
|
||||||
|
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
|
||||||
|
},
|
||||||
|
}
|
||||||
119
pkg/serra/remove.go
Normal file
119
pkg/serra/remove.go
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
package serra
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/chzyer/readline"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
removeCmd.Flags().Int64VarP(&count, "count", "c", 1, "Amount of cards to remove")
|
||||||
|
removeCmd.Flags().BoolVarP(&interactive, "interactive", "i", false, "Spin up interactive terminal")
|
||||||
|
removeCmd.Flags().StringVarP(&set, "set", "s", "", "Filter by set code (usg/mmq/vow)")
|
||||||
|
removeCmd.Flags().BoolVarP(&foil, "foil", "f", false, "Remove foil variant of card")
|
||||||
|
rootCmd.AddCommand(removeCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
var removeCmd = &cobra.Command{
|
||||||
|
Aliases: []string{"a"},
|
||||||
|
Use: "remove",
|
||||||
|
Short: "Remove a card from your collection",
|
||||||
|
Long: "Removes a card from your collection. Amount can be modified using flags",
|
||||||
|
SilenceErrors: true,
|
||||||
|
RunE: func(cmd *cobra.Command, cards []string) error {
|
||||||
|
|
||||||
|
if interactive {
|
||||||
|
removeCardsInteractive(set)
|
||||||
|
} else {
|
||||||
|
removeCards(cards, count)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeCardsInteractive(set string) {
|
||||||
|
l := Logger()
|
||||||
|
|
||||||
|
if len(set) == 0 {
|
||||||
|
l.Fatal("Option --set must be given in interactive mode")
|
||||||
|
}
|
||||||
|
|
||||||
|
rl, err := readline.New(fmt.Sprintf("%s> ", set))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer rl.Close()
|
||||||
|
|
||||||
|
for {
|
||||||
|
line, err := rl.Readline()
|
||||||
|
if err != nil { // io.EOF
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// construct card input for addCards
|
||||||
|
card := []string{}
|
||||||
|
card = append(card, fmt.Sprintf("%s/%s", set, strings.TrimSpace(line)))
|
||||||
|
|
||||||
|
removeCards(card, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
l.Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if foil && c.SerraCountFoil < 1 {
|
||||||
|
l.Errorf("No foil \"%s\" in the collection", c.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !foil && c.SerraCount < 1 {
|
||||||
|
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})
|
||||||
|
l.Infof("\"%s\" (%.2f%s) removed", c.Name, c.getValue(foil), getCurrency())
|
||||||
|
} else {
|
||||||
|
modifyCardCount(coll, c, -count, foil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
52
pkg/serra/root.go
Normal file
52
pkg/serra/root.go
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
// Package serra
|
||||||
|
//
|
||||||
|
// It implements base functions and also cli wrappers
|
||||||
|
// The entire tool consists only of this one package.
|
||||||
|
package serra
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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
|
||||||
|
reserved bool
|
||||||
|
set string
|
||||||
|
sinceBeginning bool
|
||||||
|
sinceLastUpdate bool
|
||||||
|
sortby string
|
||||||
|
unique bool
|
||||||
|
)
|
||||||
|
|
||||||
|
var rootCmd = &cobra.Command{
|
||||||
|
Version: Version,
|
||||||
|
Long: `serra - Magic: The Gathering Collection Tracker`,
|
||||||
|
Use: "serra",
|
||||||
|
DisableFlagsInUseLine: true,
|
||||||
|
SilenceErrors: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
func Execute() {
|
||||||
|
|
||||||
|
l := Logger()
|
||||||
|
if err := rootCmd.Execute(); err != nil {
|
||||||
|
l.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
373
pkg/serra/scryfall.go
Normal file
373
pkg/serra/scryfall.go
Normal file
@ -0,0 +1,373 @@
|
|||||||
|
package serra
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Card struct {
|
||||||
|
// Added by Serra
|
||||||
|
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"`
|
||||||
|
Colors []string `json:"colors"`
|
||||||
|
Digital bool `json:"digital"`
|
||||||
|
EdhrecRank int64 `json:"edhrec_rank"`
|
||||||
|
Finishes []string `json:"finishes"`
|
||||||
|
Foil bool `json:"foil"`
|
||||||
|
Frame string `json:"frame"`
|
||||||
|
FullArt bool `json:"full_art"`
|
||||||
|
Games []string `json:"games"`
|
||||||
|
HighresImage bool `json:"highres_image"`
|
||||||
|
ID string `json:"id" bson:"_id"`
|
||||||
|
IllustrationID string `json:"illustration_id"`
|
||||||
|
ImageStatus string `json:"image_status"`
|
||||||
|
ImageUris struct {
|
||||||
|
ArtCrop string `json:"art_crop"`
|
||||||
|
BorderCrop string `json:"border_crop"`
|
||||||
|
Large string `json:"large"`
|
||||||
|
Normal string `json:"normal"`
|
||||||
|
Png string `json:"png"`
|
||||||
|
Small string `json:"small"`
|
||||||
|
} `json:"image_uris"`
|
||||||
|
Keywords []interface{} `json:"keywords"`
|
||||||
|
Lang string `json:"lang"`
|
||||||
|
Layout string `json:"layout"`
|
||||||
|
Legalities struct {
|
||||||
|
Alchemy string `json:"alchemy"`
|
||||||
|
Brawl string `json:"brawl"`
|
||||||
|
Commander string `json:"commander"`
|
||||||
|
Duel string `json:"duel"`
|
||||||
|
Future string `json:"future"`
|
||||||
|
Gladiator string `json:"gladiator"`
|
||||||
|
Historic string `json:"historic"`
|
||||||
|
Historicbrawl string `json:"historicbrawl"`
|
||||||
|
Legacy string `json:"legacy"`
|
||||||
|
Modern string `json:"modern"`
|
||||||
|
Oldschool string `json:"oldschool"`
|
||||||
|
Pauper string `json:"pauper"`
|
||||||
|
Paupercommander string `json:"paupercommander"`
|
||||||
|
Penny string `json:"penny"`
|
||||||
|
Pioneer string `json:"pioneer"`
|
||||||
|
Premodern string `json:"premodern"`
|
||||||
|
Standard string `json:"standard"`
|
||||||
|
Vintage string `json:"vintage"`
|
||||||
|
} `json:"legalities"`
|
||||||
|
ManaCost string `json:"mana_cost"`
|
||||||
|
MultiverseIds []interface{} `json:"multiverse_ids"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Nonfoil bool `json:"nonfoil"`
|
||||||
|
Object string `json:"object"`
|
||||||
|
OracleID string `json:"oracle_id"`
|
||||||
|
OracleText string `json:"oracle_text"`
|
||||||
|
Oversized bool `json:"oversized"`
|
||||||
|
Prices PriceEntry `json:"prices"`
|
||||||
|
PrintedName string `json:"printed_name"`
|
||||||
|
PrintedText string `json:"printed_text"`
|
||||||
|
PrintedTypeLine string `json:"printed_type_line"`
|
||||||
|
PrintsSearchURI string `json:"prints_search_uri"`
|
||||||
|
Promo bool `json:"promo"`
|
||||||
|
PurchaseUris struct {
|
||||||
|
Cardhoarder string `json:"cardhoarder"`
|
||||||
|
Cardmarket string `json:"cardmarket"`
|
||||||
|
Tcgplayer string `json:"tcgplayer"`
|
||||||
|
} `json:"purchase_uris"`
|
||||||
|
Rarity string `json:"rarity"`
|
||||||
|
RelatedUris struct {
|
||||||
|
Edhrec string `json:"edhrec"`
|
||||||
|
Mtgtop8 string `json:"mtgtop8"`
|
||||||
|
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"`
|
||||||
|
TCGPlayerID float64 `json:"tcgplayer_id"`
|
||||||
|
TypeLine string `json:"type_line"`
|
||||||
|
URI string `json:"uri"`
|
||||||
|
Variation bool `json:"variation"`
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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)
|
||||||
|
}
|
||||||
|
// 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 {
|
||||||
|
Date primitive.DateTime `bson:"date"`
|
||||||
|
Eur float64 `json:"eur,string" bson:"eur,float64"`
|
||||||
|
EurFoil float64 `json:"eur_foil,string" bson:"eur_foil,float64"`
|
||||||
|
Tix float64 `json:"tix,string" bson:"tix,float64"`
|
||||||
|
Usd float64 `json:"usd,string" bson:"usd,float64"`
|
||||||
|
UsdEtched float64 `json:"usd_etched,string" bson:"usd_etched,float64"`
|
||||||
|
UsdFoil float64 `json:"usd_foil,string" bson:"usd_foil,float64"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets
|
||||||
|
|
||||||
|
type SetList struct {
|
||||||
|
Data []Set `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Set struct {
|
||||||
|
SerraPrices []PriceEntry `bson:"serra_prices"`
|
||||||
|
SerraCreated primitive.DateTime `bson:"serra_created"`
|
||||||
|
SerraUpdated primitive.DateTime `bson:"serra_updated"`
|
||||||
|
CardCount int64 `json:"card_count" bson:"cardcount"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
Digital bool `json:"digital"`
|
||||||
|
FoilOnly bool `json:"foil_only"`
|
||||||
|
IconSvgURI string `json:"icon_svg_uri"`
|
||||||
|
ID string `json:"id" bson:"_id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
NonfoilOnly bool `json:"nonfoil_only"`
|
||||||
|
Object string `json:"object"`
|
||||||
|
ReleasedAt string `json:"released_at"`
|
||||||
|
ScryfallURI string `json:"scryfall_uri"`
|
||||||
|
SearchURI string `json:"search_uri"`
|
||||||
|
SetType string `json:"set_type"`
|
||||||
|
TcgplayerID int64 `json:"tcgplayer_id"`
|
||||||
|
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 {
|
||||||
|
log.Fatalln(err)
|
||||||
|
return &Card{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
return &Card{}, fmt.Errorf("Card %s/%s not found", setName, collectorNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
//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
|
||||||
|
}
|
||||||
|
|
||||||
|
r := bytes.NewReader(body)
|
||||||
|
decoder := json.NewDecoder(r)
|
||||||
|
val := &Card{}
|
||||||
|
err = decoder.Decode(val)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("%s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set created Time
|
||||||
|
val.SerraCreated = primitive.NewDateTimeFromTime(time.Now())
|
||||||
|
|
||||||
|
// Increase Price
|
||||||
|
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...
|
||||||
|
resp, err := http.Get("https://api.scryfall.com/sets")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
return &SetList{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
return &SetList{}, fmt.Errorf("/sets not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
//We Read the response body on the line below.
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
return &SetList{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
r := bytes.NewReader(body)
|
||||||
|
decoder := json.NewDecoder(r)
|
||||||
|
val := &SetList{}
|
||||||
|
|
||||||
|
err = decoder.Decode(val)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
197
pkg/serra/set.go
Normal file
197
pkg/serra/set.go
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
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() {
|
||||||
|
setCmd.Flags().StringVarP(&sortby, "sort", "s", "release", "How to sort cards (release/value)")
|
||||||
|
rootCmd.AddCommand(setCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
var setCmd = &cobra.Command{
|
||||||
|
Aliases: []string{"cards"},
|
||||||
|
Use: "set [set]",
|
||||||
|
Short: "Search & show sets from your collection",
|
||||||
|
Long: `Search and show sets from your collection.
|
||||||
|
If you directly put a setcode as an argument, it will be displayed
|
||||||
|
otherwise you'll get a list of sets as a search result.`,
|
||||||
|
SilenceErrors: true,
|
||||||
|
RunE: func(cmd *cobra.Command, set []string) error {
|
||||||
|
if len(set) == 0 {
|
||||||
|
setList := Sets(sortby)
|
||||||
|
showSetList(setList)
|
||||||
|
} else {
|
||||||
|
ShowSet(set[0])
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func Sets(sort string) []primitive.M {
|
||||||
|
|
||||||
|
client := storageConnect()
|
||||||
|
coll := &Collection{client.Database("serra").Collection("cards")}
|
||||||
|
defer storageDisconnect(client)
|
||||||
|
|
||||||
|
groupStage := bson.D{
|
||||||
|
{"$group", bson.D{
|
||||||
|
{"_id", "$setname"},
|
||||||
|
{"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"}}}}}},
|
||||||
|
{"unique", bson.D{{"$sum", 1}}},
|
||||||
|
{"code", bson.D{{"$last", "$set"}}},
|
||||||
|
{"release", bson.D{{"$last", "$releasedat"}}},
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
var sortStage bson.D
|
||||||
|
switch sort {
|
||||||
|
case "release":
|
||||||
|
sortStage = bson.D{
|
||||||
|
{"$sort", bson.D{
|
||||||
|
{"release", 1},
|
||||||
|
}}}
|
||||||
|
case "value":
|
||||||
|
sortStage = bson.D{
|
||||||
|
{"$sort", bson.D{
|
||||||
|
{"value", 1},
|
||||||
|
}}}
|
||||||
|
}
|
||||||
|
|
||||||
|
sets, _ := coll.storageAggregate(mongo.Pipeline{groupStage, sortStage})
|
||||||
|
return sets
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func showSetList(sets []primitive.M) {
|
||||||
|
|
||||||
|
client := storageConnect()
|
||||||
|
setscoll := &Collection{client.Database("serra").Collection("sets")}
|
||||||
|
|
||||||
|
for _, set := range sets {
|
||||||
|
setobj, _ := findSetByCode(setscoll, set["code"].(string))
|
||||||
|
fmt.Printf("* %s %s%s%s (%s%s%s)\n", set["release"].(string)[0:4], Purple, set["_id"], Reset, Cyan, set["code"], Reset)
|
||||||
|
fmt.Printf(" Cards: %s%d/%d%s Total: %.0f \n", Yellow, set["unique"], setobj.CardCount, Reset, set["count"])
|
||||||
|
fmt.Printf(" Value: %s%.2f%s%s\n", Pink, set["value"], getCurrency(), Reset)
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
cardSortCurrency = bson.D{{"prices.eur", -1}}
|
||||||
|
}
|
||||||
|
cards, err := coll.storageFind(bson.D{{"set", setname}}, cardSortCurrency, 0, 0)
|
||||||
|
if (err != nil) || len(cards) == 0 {
|
||||||
|
l.Errorf("Set %s not found or no card in your collection.", setname)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch set informations
|
||||||
|
setcoll := &Collection{client.Database("serra").Collection("sets")}
|
||||||
|
sets, _ := setcoll.storageFindSet(bson.D{{"code", setname}}, bson.D{{"_id", 1}})
|
||||||
|
|
||||||
|
// set values
|
||||||
|
matchStage := bson.D{
|
||||||
|
{"$match", bson.D{
|
||||||
|
{"set", setname},
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
groupStage := bson.D{
|
||||||
|
{"$group", bson.D{
|
||||||
|
{"_id", "$setname"},
|
||||||
|
{"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", bson.D{{"$multiply", bson.A{1.0, "$serra_count_foil"}}}}}},
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
stats, _ := coll.storageAggregate(mongo.Pipeline{matchStage, groupStage})
|
||||||
|
|
||||||
|
// set rarities
|
||||||
|
matchStage = bson.D{
|
||||||
|
{"$match", bson.D{
|
||||||
|
{"set", setname},
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
groupStage = bson.D{
|
||||||
|
{"$group", bson.D{
|
||||||
|
{"_id", "$rarity"},
|
||||||
|
{"count", bson.D{{"$sum", bson.D{{"$multiply", bson.A{1.0, "$serra_count"}}}}}},
|
||||||
|
}}}
|
||||||
|
|
||||||
|
sortStage := bson.D{
|
||||||
|
{"$sort", bson.D{
|
||||||
|
{"_id", 1},
|
||||||
|
}}}
|
||||||
|
rar, _ := coll.storageAggregate(mongo.Pipeline{matchStage, groupStage, sortStage})
|
||||||
|
|
||||||
|
ri := convertRarities(rar)
|
||||||
|
|
||||||
|
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"])
|
||||||
|
fmt.Printf("Foil Cards: %.0f\n", stats[0]["count_foil"])
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
totalValue := normalValue + foilValue
|
||||||
|
|
||||||
|
normalCount, _ := getFloat64(stats[0]["count"])
|
||||||
|
foilCount, _ := getFloat64(stats[0]["count_foil"])
|
||||||
|
|
||||||
|
fmt.Printf("\n%sCurrent Value%s\n", Purple, Reset)
|
||||||
|
fmt.Printf("Total: %.0fx %s%.2f%s%s\n", normalCount+foilCount, Yellow, totalValue, getCurrency(), Reset)
|
||||||
|
fmt.Printf("Normal: %.0fx %s%.2f%s%s\n", stats[0]["count"], Yellow, normalValue, getCurrency(), Reset)
|
||||||
|
fmt.Printf("Foil: %.0fx %s%.2f%s%s\n", stats[0]["count_foil"], Yellow, foilValue, getCurrency(), Reset)
|
||||||
|
|
||||||
|
fmt.Printf("\n%sRarities%s\n", Purple, Reset)
|
||||||
|
fmt.Printf("Mythics: %.0f\n", ri.Mythics)
|
||||||
|
fmt.Printf("Rares: %.0f\n", ri.Rares)
|
||||||
|
fmt.Printf("Uncommons: %.0f\n", ri.Uncommons)
|
||||||
|
fmt.Printf("Commons: %.0f\n", ri.Commons)
|
||||||
|
|
||||||
|
fmt.Printf("\n%sPrice History:%s\n", Pink, Reset)
|
||||||
|
showPriceHistory(sets[0].SerraPrices, "* ", true)
|
||||||
|
|
||||||
|
fmt.Printf("\n%sMost valuable cards%s\n", Pink, Reset)
|
||||||
|
|
||||||
|
// Calc counter to show 10 cards or less
|
||||||
|
ccards := 0
|
||||||
|
if len(cards) < 10 {
|
||||||
|
ccards = len(cards)
|
||||||
|
} else {
|
||||||
|
ccards = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < ccards; i++ {
|
||||||
|
card := cards[i]
|
||||||
|
fmt.Printf("* %s%s%s (%s/%s) %s%.2f%s%s\n", Purple, card.Name, Reset, sets[0].Code, card.CollectorNumber, Yellow, card.getValue(false), getCurrency(), Reset)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -18,27 +17,48 @@ type Total struct {
|
|||||||
Value []PriceEntry `bson:"value"`
|
Value []PriceEntry `bson:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://siongui.github.io/2017/02/11/go-add-method-function-to-type-in-external-package/
|
// Collection Struct
|
||||||
|
// reason: https://siongui.github.io/2017/02/11/go-add-method-function-to-type-in-external-package/
|
||||||
type Collection struct {
|
type Collection struct {
|
||||||
*mongo.Collection
|
*mongo.Collection
|
||||||
}
|
}
|
||||||
|
|
||||||
func storage_connect() *mongo.Client {
|
// Returns configured human readable name for
|
||||||
|
// the configured currency of the user
|
||||||
uri := os.Getenv("MONGODB_URI")
|
func getCurrencyField(foil bool) string {
|
||||||
if uri == "" {
|
switch os.Getenv("SERRA_CURRENCY") {
|
||||||
log.Fatal("You must set your 'MONGODB_URI' environmental variable. See\n\t https://docs.mongodb.com/drivers/go/current/usage-examples/#environment-variable")
|
case "EUR":
|
||||||
|
if foil {
|
||||||
|
return "$prices.eur_foil"
|
||||||
|
}
|
||||||
|
return "$prices.eur"
|
||||||
|
case "USD":
|
||||||
|
if foil {
|
||||||
|
return "$prices.usd_foil"
|
||||||
|
}
|
||||||
|
return "$prices.usd"
|
||||||
|
default:
|
||||||
|
if foil {
|
||||||
|
return "$prices.usd_foil"
|
||||||
|
}
|
||||||
|
return "$prices.usd"
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func storageConnect() *mongo.Client {
|
||||||
|
l := Logger()
|
||||||
|
uri := getMongoDBURI()
|
||||||
|
|
||||||
client, err := mongo.Connect(context.TODO(), options.Client().ApplyURI(uri))
|
client, err := mongo.Connect(context.TODO(), options.Client().ApplyURI(uri))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
l.Fatalf("Could not connect to mongodb at %s", uri)
|
||||||
}
|
}
|
||||||
|
|
||||||
return client
|
return client
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (coll Collection) storage_add(card *Card) error {
|
func (coll Collection) storageAdd(card *Card) error {
|
||||||
|
|
||||||
card.SerraUpdated = primitive.NewDateTimeFromTime(time.Now())
|
card.SerraUpdated = primitive.NewDateTimeFromTime(time.Now())
|
||||||
|
|
||||||
@ -50,30 +70,24 @@ func (coll Collection) storage_add(card *Card) error {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (coll Collection) storage_add_set(set *Set) (*mongo.InsertOneResult, error) {
|
func (coll Collection) storageAddSet(set *Set) (*mongo.InsertOneResult, error) {
|
||||||
|
|
||||||
id, err := coll.InsertOne(context.TODO(), set)
|
id, err := coll.InsertOne(context.TODO(), set)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return id, err
|
return id, err
|
||||||
}
|
}
|
||||||
return &mongo.InsertOneResult{}, nil
|
return id, err
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (coll Collection) storage_add_total(v float64) error {
|
func (coll Collection) storageAddTotal(p PriceEntry) error {
|
||||||
|
|
||||||
// create total object if not exists...
|
// create total object if not exists...
|
||||||
coll.InsertOne(context.TODO(), Total{ID: "1", Value: []PriceEntry{}})
|
coll.InsertOne(context.TODO(), Total{ID: "1", Value: []PriceEntry{}})
|
||||||
|
|
||||||
// update object as intended...
|
// update object as intended...
|
||||||
filter := bson.D{{"_id", "1"}}
|
filter := bson.D{{"_id", "1"}}
|
||||||
update := bson.M{
|
update := bson.M{"$push": bson.M{"value": p}}
|
||||||
"$push": bson.M{"value": bson.M{
|
|
||||||
"date": primitive.NewDateTimeFromTime(time.Now()),
|
|
||||||
"value": v,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := coll.UpdateOne(
|
_, err := coll.UpdateOne(
|
||||||
context.Background(),
|
context.Background(),
|
||||||
@ -87,87 +101,89 @@ func (coll Collection) storage_add_total(v float64) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (coll Collection) storage_find(filter, sort bson.D) ([]Card, error) {
|
func (coll Collection) storageFind(filter, sort bson.D, skip, limit int64) ([]Card, error) {
|
||||||
|
opts := options.Find().SetSort(sort).SetSkip(skip).SetLimit(limit)
|
||||||
opts := options.Find().SetSort(sort)
|
|
||||||
cursor, err := coll.Find(context.TODO(), filter, opts)
|
cursor, err := coll.Find(context.TODO(), filter, opts)
|
||||||
|
l := Logger()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
l.Fatalf("Could not query data due to connection errors to database: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
var results []Card
|
var results []Card
|
||||||
if err = cursor.All(context.TODO(), &results); err != nil {
|
if err = cursor.All(context.TODO(), &results); err != nil {
|
||||||
log.Fatal(err)
|
l.Fatal(err)
|
||||||
return []Card{}, err
|
return []Card{}, err
|
||||||
}
|
}
|
||||||
return results, nil
|
return results, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (coll Collection) storage_find_set(filter, sort bson.D) ([]Set, error) {
|
func (coll Collection) storageFindSet(filter, sort bson.D) ([]Set, error) {
|
||||||
|
l := Logger()
|
||||||
opts := options.Find().SetSort(sort)
|
opts := options.Find().SetSort(sort)
|
||||||
|
|
||||||
cursor, err := coll.Find(context.TODO(), filter, opts)
|
cursor, err := coll.Find(context.TODO(), filter, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
l.Fatalf("Could not query set data due to connection errors to database: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
var results []Set
|
var results []Set
|
||||||
if err = cursor.All(context.TODO(), &results); err != nil {
|
if err = cursor.All(context.TODO(), &results); err != nil {
|
||||||
log.Fatal(err)
|
l.Fatal(err)
|
||||||
return []Set{}, err
|
return []Set{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return results, nil
|
return results, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (coll Collection) storage_find_total() (Total, error) {
|
func (coll Collection) storageFindTotal() (Total, error) {
|
||||||
|
|
||||||
var total Total
|
var total Total
|
||||||
|
l := Logger()
|
||||||
|
|
||||||
err := coll.FindOne(context.TODO(), bson.D{{"_id", "1"}}).Decode(&total)
|
err := coll.FindOne(context.TODO(), bson.D{{"_id", "1"}}).Decode(&total)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Total{}, err
|
l.Fatalf("Could not query total data due to connection errors to database: %s", err.Error())
|
||||||
}
|
}
|
||||||
return total, nil
|
|
||||||
|
|
||||||
|
return total, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (coll Collection) storage_remove(filter bson.M) error {
|
func (coll Collection) storageRemove(filter bson.M) error {
|
||||||
|
l := Logger()
|
||||||
|
|
||||||
_, err := coll.DeleteOne(context.TODO(), filter)
|
_, err := coll.DeleteOne(context.TODO(), filter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
l.Fatalf("Could remove card data due to connection errors to database: %s", err.Error())
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (coll Collection) storage_aggregate(pipeline mongo.Pipeline) ([]primitive.M, error) {
|
func (coll Collection) storageAggregate(pipeline mongo.Pipeline) ([]primitive.M, error) {
|
||||||
|
l := Logger()
|
||||||
|
opts := options.Aggregate()
|
||||||
|
|
||||||
// db.cards.aggregate([ {$group: { _id: "$setname", sum: { $sum: "$prices.eur"}}}])
|
|
||||||
opts := options.Aggregate().SetMaxTime(2 * time.Second)
|
|
||||||
cursor, err := coll.Aggregate(
|
cursor, err := coll.Aggregate(
|
||||||
context.TODO(),
|
context.TODO(),
|
||||||
pipeline,
|
pipeline,
|
||||||
opts)
|
opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
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.
|
// Get a list of all returned documents and print them out.
|
||||||
// See the mongo.Cursor documentation for more examples of using cursors.
|
// See the mongo.Cursor documentation for more examples of using cursors.
|
||||||
var results []bson.M
|
var results []bson.M
|
||||||
if err = cursor.All(context.TODO(), &results); err != nil {
|
if err = cursor.All(context.TODO(), &results); err != nil {
|
||||||
log.Fatal(err)
|
l.Fatal(err)
|
||||||
}
|
}
|
||||||
return results, nil
|
|
||||||
|
|
||||||
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (coll Collection) storage_update(filter, update bson.M) error {
|
func (coll Collection) storageUpdate(filter, update bson.M) error {
|
||||||
|
l := Logger()
|
||||||
// Call the driver's UpdateOne() method and pass filter and update to it
|
// Call the driver's UpdateOne() method and pass filter and update to it
|
||||||
_, err := coll.UpdateOne(
|
_, err := coll.UpdateOne(
|
||||||
context.Background(),
|
context.Background(),
|
||||||
@ -175,14 +191,13 @@ func (coll Collection) storage_update(filter, update bson.M) error {
|
|||||||
update,
|
update,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
l.Fatalf("Could not update data due to connection errors to database: %s", err.Error())
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func storage_disconnect(client *mongo.Client) error {
|
func storageDisconnect(client *mongo.Client) error {
|
||||||
if err := client.Disconnect(context.TODO()); err != nil {
|
if err := client.Disconnect(context.TODO()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
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
|
||||||
|
},
|
||||||
|
}
|
||||||
161
pkg/serra/update.go
Normal file
161
pkg/serra/update.go
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
package serra
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
|
"github.com/schollz/progressbar/v3"
|
||||||
|
"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(updateCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
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.`,
|
||||||
|
SilenceErrors: true,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
|
||||||
|
client := storageConnect()
|
||||||
|
l := Logger()
|
||||||
|
defer storageDisconnect(client)
|
||||||
|
|
||||||
|
// update sets
|
||||||
|
setscoll := &Collection{client.Database("serra").Collection("sets")}
|
||||||
|
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},
|
||||||
|
{"serra_count_foil", true},
|
||||||
|
{"set", true},
|
||||||
|
{"last_price", bson.D{{"$arrayElemAt", bson.A{"$serra_prices", -1}}}}}}}
|
||||||
|
groupStage := bson.D{
|
||||||
|
{"$group", bson.D{
|
||||||
|
{"_id", ""},
|
||||||
|
{"eur", bson.D{{"$sum", bson.D{{"$multiply", bson.A{"$last_price.eur", "$serra_count"}}}}}},
|
||||||
|
{"eurfoil", bson.D{{"$sum", bson.D{{"$multiply", bson.A{"$last_price.eur_foil", "$serra_count_foil"}}}}}},
|
||||||
|
{"usd", bson.D{{"$sum", bson.D{{"$multiply", bson.A{"$last_price.usd", "$serra_count"}}}}}},
|
||||||
|
{"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)
|
||||||
|
|
||||||
|
cards, _ := coll.storageFind(bson.D{{"set", set.Code}}, bson.D{{"_id", 1}}, 0, 0)
|
||||||
|
|
||||||
|
// if no cards in collection for this set, skip it
|
||||||
|
if len(cards) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
bar := progressbar.NewOptions(len(cards),
|
||||||
|
progressbar.OptionSetWidth(50),
|
||||||
|
progressbar.OptionSetDescription(fmt.Sprintf("%s, %s%s%s\t", set.ReleasedAt[0:4], Yellow, set.Code, Reset)),
|
||||||
|
progressbar.OptionEnableColorCodes(true),
|
||||||
|
progressbar.OptionShowCount(),
|
||||||
|
progressbar.OptionSetTheme(progressbar.Theme{
|
||||||
|
Saucer: "[green]=[reset]",
|
||||||
|
SaucerHead: "[green]>[reset]",
|
||||||
|
SaucerPadding: " ",
|
||||||
|
BarStart: "|",
|
||||||
|
BarEnd: "| " + set.Name,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, card := range cards {
|
||||||
|
bar.Add(1)
|
||||||
|
updatedCard, err := getCardFromBulk(updatedCards, card.Set, card.CollectorNumber)
|
||||||
|
if err != nil {
|
||||||
|
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, "cardmarketid": updatedCard.CardmarketID, "tcgplayerid": updatedCard.TCGPlayerID},
|
||||||
|
"$push": bson.M{"serra_prices": updatedCard.Prices},
|
||||||
|
}
|
||||||
|
coll.storageUpdate(bson.M{"_id": bson.M{"$eq": card.ID}}, update)
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
// update set value sum
|
||||||
|
|
||||||
|
// calculate value summary
|
||||||
|
matchStage := bson.D{{"$match", bson.D{{"set", set.Code}}}}
|
||||||
|
setValue, _ := coll.storageAggregate(mongo.Pipeline{matchStage, projectStage, groupStage})
|
||||||
|
|
||||||
|
p := PriceEntry{}
|
||||||
|
s := setValue[0]
|
||||||
|
|
||||||
|
p.Date = primitive.NewDateTimeFromTime(time.Now())
|
||||||
|
|
||||||
|
// fill struct PriceEntry with map from mongoresult
|
||||||
|
mapstructure.Decode(s, &p)
|
||||||
|
|
||||||
|
// do the update
|
||||||
|
setUpdate := bson.M{
|
||||||
|
"$set": bson.M{"serra_updated": p.Date, "cardcount": set.CardCount},
|
||||||
|
"$push": bson.M{"serra_prices": p},
|
||||||
|
}
|
||||||
|
setscoll.storageUpdate(bson.M{"code": bson.M{"$eq": set.Code}}, setUpdate)
|
||||||
|
}
|
||||||
|
|
||||||
|
totalValue, _ := coll.storageAggregate(mongo.Pipeline{projectStage, groupStage})
|
||||||
|
|
||||||
|
t := PriceEntry{}
|
||||||
|
t.Date = primitive.NewDateTimeFromTime(time.Now())
|
||||||
|
mapstructure.Decode(totalValue[0], &t)
|
||||||
|
|
||||||
|
// This is here to be able to fetch currency from
|
||||||
|
// constructed new priceentry
|
||||||
|
tmpCard := Card{}
|
||||||
|
tmpCard.Prices = t
|
||||||
|
|
||||||
|
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
|
||||||
|
},
|
||||||
|
}
|
||||||
121
pkg/serra/web.go
Normal file
121
pkg/serra/web.go
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
package serra
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
webCmd.Flags().StringVarP(&address, "address", "a", "0.0.0.0", "Address to listen on")
|
||||||
|
webCmd.Flags().Uint64VarP(&port, "port", "p", 8080, "Port to listen on")
|
||||||
|
rootCmd.AddCommand(webCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func add(a, b int64) int64 {
|
||||||
|
return a + b
|
||||||
|
}
|
||||||
|
|
||||||
|
var webCmd = &cobra.Command{
|
||||||
|
Aliases: []string{"a"},
|
||||||
|
Use: "web",
|
||||||
|
Short: "Startup web interface",
|
||||||
|
Long: "Start a tiny web interface to have a web view of your collection",
|
||||||
|
SilenceErrors: true,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
startWeb()
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
type Query struct {
|
||||||
|
Name string `form:"name"`
|
||||||
|
Set string `form:"set"`
|
||||||
|
Sort string `form:"sort"`
|
||||||
|
Limit int64 `form:"limit"`
|
||||||
|
Page int64 `form:"page"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func startWeb() error {
|
||||||
|
router := gin.Default()
|
||||||
|
router.SetFuncMap(template.FuncMap{
|
||||||
|
"add": add,
|
||||||
|
})
|
||||||
|
router.LoadHTMLGlob("templates/*.tmpl")
|
||||||
|
router.Static("/assets", "./assets")
|
||||||
|
|
||||||
|
// Landing page
|
||||||
|
router.GET("/", landingPage)
|
||||||
|
|
||||||
|
router.Run(address + ":" + strconv.FormatUint(port, 10))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func landingPage(c *gin.Context) {
|
||||||
|
var query Query
|
||||||
|
if c.ShouldBind(&query) == nil {
|
||||||
|
|
||||||
|
// Construct per Page results "limit"
|
||||||
|
strLimit := c.DefaultQuery("limit", "500")
|
||||||
|
limit, _ := strconv.ParseInt(strLimit, 10, 64)
|
||||||
|
if limit == 0 {
|
||||||
|
limit = 500
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch all sets for Dropdown
|
||||||
|
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, false)
|
||||||
|
|
||||||
|
// Construct quick way for counting results
|
||||||
|
filter := bson.D{}
|
||||||
|
client := storageConnect()
|
||||||
|
coll := &Collection{client.Database("serra").Collection("cards")}
|
||||||
|
|
||||||
|
if query.Set != "" {
|
||||||
|
filter = append(filter, bson.E{"set", query.Set})
|
||||||
|
}
|
||||||
|
|
||||||
|
if query.Name != "" {
|
||||||
|
filter = append(filter, bson.E{"name", bson.D{{"$regex", ".*" + query.Name + ".*"}, {"$options", "i"}}})
|
||||||
|
}
|
||||||
|
|
||||||
|
counts, _ := coll.storageAggregate(mongo.Pipeline{
|
||||||
|
bson.D{
|
||||||
|
{"$match", filter},
|
||||||
|
},
|
||||||
|
bson.D{
|
||||||
|
{"$group", bson.D{
|
||||||
|
{"_id", nil},
|
||||||
|
{"count", bson.D{{"$sum", 1}}},
|
||||||
|
}}},
|
||||||
|
})
|
||||||
|
defer storageDisconnect(client)
|
||||||
|
|
||||||
|
// 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",
|
||||||
|
"cards": cards,
|
||||||
|
"sets": sets,
|
||||||
|
"query": query,
|
||||||
|
"version": Version,
|
||||||
|
"prevPage": query.Page - 1,
|
||||||
|
"page": query.Page,
|
||||||
|
"nextPage": query.Page + 1,
|
||||||
|
"limit": limit,
|
||||||
|
"numCards": int64(numCards),
|
||||||
|
"numPages": int64(numCards) / limit,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
169
readme.md
169
readme.md
@ -11,23 +11,24 @@ Collection Tracker Websites that are:
|
|||||||
|
|
||||||
So I started my own Collection Tracker using [Golang](https://golang.org),
|
So I started my own Collection Tracker using [Golang](https://golang.org),
|
||||||
[MongoDB](https://mongodb.com) and [Scryfall](https://scryfall.com) to have
|
[MongoDB](https://mongodb.com) and [Scryfall](https://scryfall.com) to have
|
||||||
an overview in what cards you own and what value they are.
|
an overview in what cards you own and what value they have.
|
||||||
|
|
||||||
## What Serra does
|
**What Serra does**
|
||||||
|
|
||||||
* Tracks prices
|
* Tracks prices
|
||||||
* Calculates statistics
|
* Calculates statistics
|
||||||
* Query/filter all of your cards
|
* Query/filter all of your cards
|
||||||
* Shows what cards/sets do best in value development.
|
* Shows what cards/sets do best in value development.
|
||||||
|
|
||||||
## What Serra does not
|
**What Serra does not**
|
||||||
|
|
||||||
* Does not give a shit about conditions (NM, M, GD...)
|
* Does not care about conditions (NM, M, GD...)
|
||||||
* Does not track if card is foil or not (may come in the future)
|
* Does not track etched cards. Only normal and foil.
|
||||||
* Is not configurable to have Dollar/US Prices
|
|
||||||
|
|
||||||
# Quickstart
|
# Quickstart
|
||||||
|
|
||||||
|
## Install Binaries
|
||||||
|
|
||||||
on macOS you can use
|
on macOS you can use
|
||||||
|
|
||||||
brew install noqqe/tap/serra
|
brew install noqqe/tap/serra
|
||||||
@ -36,15 +37,25 @@ on Linux/BSD/Windows you can download binaries from
|
|||||||
|
|
||||||
https://github.com/noqqe/serra/releases
|
https://github.com/noqqe/serra/releases
|
||||||
|
|
||||||
After that you need to spin up a MongoDB yourself or use the docker-compose
|
## Spin up Database
|
||||||
setup included in this Repo:
|
|
||||||
|
To run serra, a MongoDB Database is required. The best way is to setup one by yourself. Any way it connects is fine.
|
||||||
|
|
||||||
|
You can also use the docker-compose setup included in this Repo:
|
||||||
|
|
||||||
docker-compose up -d
|
docker-compose up -d
|
||||||
|
|
||||||
|
## Configure the Database
|
||||||
|
|
||||||
|
Configure `serra` via Environment variables
|
||||||
|
|
||||||
export MONGODB_URI='mongodb://root:root@localhost:27017'
|
export MONGODB_URI='mongodb://root:root@localhost:27017'
|
||||||
|
export SERRA_CURRENCY=USD # or EUR
|
||||||
|
|
||||||
After that, you can add a card
|
After that, you can add a card
|
||||||
|
|
||||||
./serra add usg/17
|
./serra add usg/17
|
||||||
|
./serra update
|
||||||
|
|
||||||
Start exploring :) (the more cards you add, the more fun it is)
|
Start exploring :) (the more cards you add, the more fun it is)
|
||||||
|
|
||||||
@ -52,19 +63,29 @@ Start exploring :) (the more cards you add, the more fun it is)
|
|||||||
|
|
||||||
The overall usage is described in `--help` text. But below are some examples.
|
The overall usage is described in `--help` text. But below are some examples.
|
||||||
```
|
```
|
||||||
./serra
|
|
||||||
Usage:
|
Usage:
|
||||||
serra add <cardid>... [--count=<number>]
|
serra [command]
|
||||||
serra remove <cardid>...
|
|
||||||
serra cards [--rarity=<rarity>] [--set=<setcode>] [--sort=<sort>]
|
Available Commands:
|
||||||
serra card <cardid>...
|
add Add a card to your collection
|
||||||
serra tops [--limit=<limit>]
|
card Search & show cards from your collection
|
||||||
serra flops [--limit=<limit>]
|
check Check if a card is in your collection
|
||||||
serra missing <setcode>
|
completion Generate the autocompletion script for the specified shell
|
||||||
serra set <setcode>
|
flops What cards lost most value
|
||||||
serra sets
|
help Help about any command
|
||||||
serra update
|
missing Display missing cards from a set
|
||||||
serra stats
|
remove Remove a card from your collection
|
||||||
|
set Search & show sets from your collection
|
||||||
|
stats Shows statistics of the collection
|
||||||
|
tops What cards gained most value
|
||||||
|
update Update card values from scryfall
|
||||||
|
web Startup web interface
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
-h, --help help for serra
|
||||||
|
-v, --version version for serra
|
||||||
|
|
||||||
|
Use "serra [command] --help" for more information about a command.
|
||||||
```
|
```
|
||||||
|
|
||||||
## Add
|
## Add
|
||||||
@ -117,27 +138,90 @@ update. After all Sets are updated, the whole collection value is updated.
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
## Check
|
||||||
|
|
||||||
|
To add a card to your collection.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## Adding all those cards, manually?
|
## Adding all those cards, manually?
|
||||||
|
|
||||||
Yes. While there are serveral OCR/Photo Scanners for mtg cards, I found they
|
Yes. While there are serveral OCR/Photo Scanners for mtg cards, I found they
|
||||||
are not accurate enough. They guess Editions wrong, they have problems with
|
are not accurate enough. They guess Editions wrong, they have problems with
|
||||||
blue/black cards and so on.
|
blue/black cards and so on.
|
||||||
|
|
||||||
I add my cards using a tiny shell wrapper, since they are sorted by editions
|
I add my cards the `add --interactive` feature, since they are sorted by editions
|
||||||
anyways.
|
anyways.
|
||||||
|
|
||||||
```
|
```
|
||||||
./add-card-wrapper.fish usg
|
> ./serra add --interactive --unique --set one
|
||||||
read> 17
|
one> 1
|
||||||
Updating Card "Herald of Serra" amount to 2
|
1x "Against All Odds" (uncommon, 0.06 USD) added to Collection.
|
||||||
|
one> 1
|
||||||
|
Not adding "Against All Odds" (uncommon, 0.06 USD) to Collection because it already exists.
|
||||||
|
one> 3
|
||||||
|
1x "Apostle of Invasion" (uncommon, 0.03 USD) added to Collection.
|
||||||
|
```
|
||||||
|
|
||||||
read> 18
|
It also supports ranges of cards
|
||||||
...
|
```
|
||||||
|
dmr> 1-3
|
||||||
|
1x "Auramancer" (common, 0.02$) added to Collection.
|
||||||
|
1x "Battle Screech" (uncommon, 0.09$) added to Collection.
|
||||||
|
1x "Cleric of the Forward Order" (common, 0.01$) added to Collection.
|
||||||
```
|
```
|
||||||
|
|
||||||
Its basically typing 2-3 digit numbers and hitting enter. I was way faster
|
Its basically typing 2-3 digit numbers and hitting enter. I was way faster
|
||||||
with this approach then Smartphone scanners.
|
with this approach then Smartphone scanners.
|
||||||
|
|
||||||
|
# Upgrade
|
||||||
|
|
||||||
|
If you want to upgrade, go to [releases](https://github.com/noqqe/serra/releases) Page and download the corresponding release for your platform.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
```
|
||||||
|
wget https://github.com/noqqe/serra/releases/download/3.10.0/serra_Darwin_x86_64.tar.gz
|
||||||
|
tar zxfv serra_Darwin_x86_64.tar.gz
|
||||||
|
./serra
|
||||||
|
```
|
||||||
|
|
||||||
|
## Upgrade Notes
|
||||||
|
|
||||||
|
### 2.x.x -> 3.x.x
|
||||||
|
|
||||||
|
No extra steps needed. Only new Webinterface and Foil support
|
||||||
|
|
||||||
|
### 1.5.3 -> 2.0.0
|
||||||
|
|
||||||
|
In this stage of the development of serra, I was breaking the original
|
||||||
|
database "schema" without migration.
|
||||||
|
|
||||||
|
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
|
# Development
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
@ -145,39 +229,6 @@ with this approach then Smartphone scanners.
|
|||||||
go build .
|
go build .
|
||||||
./serra
|
./serra
|
||||||
|
|
||||||
## Cheatsheet Queries
|
|
||||||
|
|
||||||
Find cards that increased prices
|
|
||||||
|
|
||||||
db.cards.find({$expr: {$gt: [{$arrayElemAt: ["$serra_prices", -2]}, {$arrayElemAt: ["$serra_prices", -1]}]}}, {name:1})
|
|
||||||
|
|
||||||
Update card Price
|
|
||||||
|
|
||||||
db.cards.update(
|
|
||||||
{'_id':'8fa2ecf9-b53c-4f1d-9028-ca3820d043cb'},
|
|
||||||
{$set:{'serra_updated':ISODate("2021-11-02T09:28:56.504Z")},
|
|
||||||
$push: {"serra_prices": { date: ISODate("2021-11-02T09:28:56.504Z"), value: 0.1 }}});
|
|
||||||
|
|
||||||
Set value
|
|
||||||
|
|
||||||
db.cards.aggregate([{ $group: { _id: { set: "$set" }, value: { $sum: { $multiply: ["$prices.eur", "$serra_count"] } }, count: { $sum: 1 } } }])
|
|
||||||
|
|
||||||
Color distribution
|
|
||||||
|
|
||||||
db.cards.aggregate([{ $group: { _id: { color: "$colors" }, count: { $sum: 1 } } }])
|
|
||||||
|
|
||||||
Calculate value of all sets
|
|
||||||
|
|
||||||
db.sets.aggregate({$match: {serra_prices: {$exists: true}}}, {$project: {name: 1, "totalValue": {$arrayElemAt: ["$serra_prices", -1]} }}, {$group: {_id: null, total: {$sum: "$totalValue.value" }}})
|
|
||||||
|
|
||||||
Calculate what cards gained most value in percent
|
|
||||||
|
|
||||||
db.cards.aggregate({$project: {set: 1, collectornumber:1, name: 1, "old": {$arrayElemAt: ["$serra_prices.value", -2]}, "current": {$arrayElemAt: ["$serra_prices.value", -1]} }}, {$match: {old: {$gt: 2}}} ,{$project: {name: 1,set:1,collectornumber:1,current:1, "rate": {$subtract: [{$divide: ["$current", {$divide: ["$old", 100]}]}, 100]} }}, {$sort: { rate: -1}})
|
|
||||||
|
|
||||||
Show when cards where added per month of the year
|
|
||||||
|
|
||||||
db.cards.aggregate({ $project: { month: { $month: "$serra_created" }, year: { $year: "$serra_created" }, name: 1 } }, { $group: { _id: { month: "$month", year: "$year" }, count: { $sum: 1 } } })
|
|
||||||
|
|
||||||
## MongoDB Operations
|
## MongoDB Operations
|
||||||
|
|
||||||
A few commands that do backups and exports of your data inside of the docker
|
A few commands that do backups and exports of your data inside of the docker
|
||||||
|
|||||||
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
|
||||||
13
scripts/replace.sh
Executable file
13
scripts/replace.sh
Executable file
@ -0,0 +1,13 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# This script replaces a normal card with a foil card I needed this because the
|
||||||
|
# tracking of foils was only added in version 3.5.0 of serra
|
||||||
|
|
||||||
|
# give set code as $1 like "dmr"
|
||||||
|
: ${1:?}
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
read -p "$1> " card
|
||||||
|
serra add --foil ${1}/${card}
|
||||||
|
serra remove ${1}/${card}
|
||||||
|
done
|
||||||
@ -1,65 +0,0 @@
|
|||||||
package serra
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"go.mongodb.org/mongo-driver/mongo"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
addCmd.Flags().Int64VarP(&count, "count", "c", 1, "Amount of cards to add")
|
|
||||||
addCmd.Flags().BoolVarP(&unique, "unique", "u", false, "Only add card if not existent yet")
|
|
||||||
rootCmd.AddCommand(addCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
var addCmd = &cobra.Command{
|
|
||||||
Aliases: []string{"a"},
|
|
||||||
Use: "add",
|
|
||||||
Short: "Add a card to your collection",
|
|
||||||
Long: "Adds a card from scryfall to your collection. Amount can be modified using flags",
|
|
||||||
SilenceErrors: true,
|
|
||||||
RunE: func(cmd *cobra.Command, cards []string) error {
|
|
||||||
|
|
||||||
client := storage_connect()
|
|
||||||
coll := &Collection{client.Database("serra").Collection("cards")}
|
|
||||||
defer storage_disconnect(client)
|
|
||||||
|
|
||||||
// Loop over different cards
|
|
||||||
for _, card := range cards {
|
|
||||||
// Fetch card from scryfall
|
|
||||||
c, err := fetch_card(card)
|
|
||||||
if err != nil {
|
|
||||||
LogMessage(fmt.Sprintf("%v", err), "red")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write card to mongodb
|
|
||||||
c.SerraCount = count
|
|
||||||
err = coll.storage_add(c)
|
|
||||||
|
|
||||||
// If duplicate key, increase count of card
|
|
||||||
if mongo.IsDuplicateKeyError(err) {
|
|
||||||
|
|
||||||
if unique {
|
|
||||||
LogMessage(fmt.Sprintf("Not adding \"%s\" (%s, %.2f Eur) to Collection because it already exists.", c.Name, c.Rarity, c.Prices.Eur), "red")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
modify_count_of_card(coll, c, count)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// If error, print error and continue
|
|
||||||
if err != nil {
|
|
||||||
LogMessage(fmt.Sprintf("%v", err), "red")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Give feedback of successfully added card
|
|
||||||
LogMessage(fmt.Sprintf("%dx \"%s\" (%s, %.2f Eur) added to Collection.", c.SerraCount, c.Name, c.Rarity, c.Prices.Eur), "green")
|
|
||||||
}
|
|
||||||
storage_disconnect(client)
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@ -1,99 +0,0 @@
|
|||||||
package serra
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"go.mongodb.org/mongo-driver/bson"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
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(&sort, "sort", "s", "name", "How to sort cards (value/number/name)")
|
|
||||||
cardCmd.Flags().StringVarP(&name, "name", "n", "", "Name of the card (regex compatible)")
|
|
||||||
rootCmd.AddCommand(cardCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
var cardCmd = &cobra.Command{
|
|
||||||
Aliases: []string{"cards"},
|
|
||||||
Use: "card [card]",
|
|
||||||
Short: "Search & show cards from your collection",
|
|
||||||
Long: `Search and show cards from your collection.
|
|
||||||
If you directly put a card as an argument, it will be displayed
|
|
||||||
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 {
|
|
||||||
Cards(rarity, set, sort, name)
|
|
||||||
} else {
|
|
||||||
ShowCard(cards)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func ShowCard(cardids []string) {
|
|
||||||
|
|
||||||
client := storage_connect()
|
|
||||||
coll := &Collection{client.Database("serra").Collection("cards")}
|
|
||||||
defer storage_disconnect(client)
|
|
||||||
|
|
||||||
for _, v := range cardids {
|
|
||||||
|
|
||||||
cards, _ := coll.storage_find(bson.D{{"set", strings.Split(v, "/")[0]}, {"collectornumber", strings.Split(v, "/")[1]}}, bson.D{{"name", 1}})
|
|
||||||
|
|
||||||
for _, card := range cards {
|
|
||||||
show_card_details(&card)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Cards(rarity, set, sort, name string) {
|
|
||||||
|
|
||||||
var total float64
|
|
||||||
client := storage_connect()
|
|
||||||
coll := &Collection{client.Database("serra").Collection("cards")}
|
|
||||||
defer storage_disconnect(client)
|
|
||||||
|
|
||||||
filter := bson.D{}
|
|
||||||
|
|
||||||
switch rarity {
|
|
||||||
case "uncommon":
|
|
||||||
filter = append(filter, bson.E{"rarity", "uncommon"})
|
|
||||||
case "common":
|
|
||||||
filter = append(filter, bson.E{"rarity", "common"})
|
|
||||||
case "rare":
|
|
||||||
filter = append(filter, bson.E{"rarity", "rare"})
|
|
||||||
}
|
|
||||||
|
|
||||||
var sortStage bson.D
|
|
||||||
switch sort {
|
|
||||||
case "value":
|
|
||||||
sortStage = bson.D{{"prices.eur", 1}}
|
|
||||||
case "number":
|
|
||||||
sortStage = bson.D{{"collectornumber", 1}}
|
|
||||||
case "name":
|
|
||||||
sortStage = bson.D{{"name", 1}}
|
|
||||||
default:
|
|
||||||
sortStage = bson.D{{"name", 1}}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(set) > 0 {
|
|
||||||
filter = append(filter, bson.E{"set", set})
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(name) > 0 {
|
|
||||||
filter = append(filter, bson.E{"name", bson.D{{"$regex", ".*" + name + ".*"}, {"$options", "i"}}})
|
|
||||||
}
|
|
||||||
|
|
||||||
cards, _ := coll.storage_find(filter, sortStage)
|
|
||||||
|
|
||||||
for _, card := range cards {
|
|
||||||
LogMessage(fmt.Sprintf("* %dx %s%s%s (%s/%s) %s%.2f EUR%s", card.SerraCount, Purple, card.Name, Reset, card.Set, card.CollectorNumber, Yellow, card.Prices.Eur, Reset), "normal")
|
|
||||||
total = total + card.Prices.Eur*float64(card.SerraCount)
|
|
||||||
}
|
|
||||||
fmt.Printf("\nTotal Value: %s%.2f EUR%s\n", Yellow, total, Reset)
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,177 +0,0 @@
|
|||||||
package serra
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"go.mongodb.org/mongo-driver/bson"
|
|
||||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Rarities struct {
|
|
||||||
Rares, Uncommons, Commons, Mythics float64
|
|
||||||
}
|
|
||||||
|
|
||||||
func modify_count_of_card(coll *Collection, c *Card, amount int64) error {
|
|
||||||
|
|
||||||
// find already existing card
|
|
||||||
sort := bson.D{{"_id", 1}}
|
|
||||||
search_filter := bson.D{{"_id", c.ID}}
|
|
||||||
stored_cards, err := coll.storage_find(search_filter, sort)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
stored_card := stored_cards[0]
|
|
||||||
|
|
||||||
// update card amount
|
|
||||||
update_filter := bson.M{"_id": bson.M{"$eq": c.ID}}
|
|
||||||
update := bson.M{
|
|
||||||
"$set": bson.M{"serra_count": stored_card.SerraCount + amount},
|
|
||||||
}
|
|
||||||
coll.storage_update(update_filter, update)
|
|
||||||
|
|
||||||
LogMessage(fmt.Sprintf("Updating Card \"%s\" amount to %d", stored_card.Name, stored_card.SerraCount+amount), "purple")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func find_card_by_setcollectornumber(coll *Collection, setcode string, collectornumber string) (*Card, error) {
|
|
||||||
|
|
||||||
sort := bson.D{{"_id", 1}}
|
|
||||||
search_filter := bson.D{{"set", setcode}, {"collectornumber", collectornumber}}
|
|
||||||
stored_cards, err := coll.storage_find(search_filter, sort)
|
|
||||||
if err != nil {
|
|
||||||
return &Card{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(stored_cards) < 1 {
|
|
||||||
return &Card{}, errors.New("Card not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &stored_cards[0], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func stringToTime(s primitive.DateTime) string {
|
|
||||||
return time.UnixMilli(int64(s)).Format("2006-01-02")
|
|
||||||
}
|
|
||||||
|
|
||||||
// missing compares two slices and returns slice of differences
|
|
||||||
func missing(a, b []string) []string {
|
|
||||||
type void struct{}
|
|
||||||
// create map with length of the 'a' slice
|
|
||||||
ma := make(map[string]void, len(a))
|
|
||||||
diffs := []string{}
|
|
||||||
// Convert first slice to map with empty struct (0 bytes)
|
|
||||||
for _, ka := range a {
|
|
||||||
ma[ka] = void{}
|
|
||||||
}
|
|
||||||
// find missing values in a
|
|
||||||
for _, kb := range b {
|
|
||||||
if _, ok := ma[kb]; !ok {
|
|
||||||
diffs = append(diffs, kb)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return diffs
|
|
||||||
}
|
|
||||||
|
|
||||||
func find_set_by_code(coll *Collection, setcode string) (*Set, error) {
|
|
||||||
|
|
||||||
stored_sets, err := coll.storage_find_set(bson.D{{"code", setcode}}, bson.D{{"_id", 1}})
|
|
||||||
if err != nil {
|
|
||||||
return &Set{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(stored_sets) < 1 {
|
|
||||||
return &Set{}, errors.New("Set not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &stored_sets[0], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func show_card_details(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("Count: %dx\n", card.SerraCount)
|
|
||||||
fmt.Printf("Rarity: %s\n", card.Rarity)
|
|
||||||
fmt.Printf("Scryfall: %s\n", strings.Replace(card.ScryfallURI, "?utm_source=api", "", 1))
|
|
||||||
fmt.Printf("Current Value: %s%.2f EUR%s\n", Yellow, card.Prices.Eur, Reset)
|
|
||||||
|
|
||||||
fmt.Printf("\n%sHistory%s\n", Green, Reset)
|
|
||||||
print_price_history(card.SerraPrices, "* ")
|
|
||||||
fmt.Println()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func convert_mana_symbols(sym []interface{}) string {
|
|
||||||
var mana string
|
|
||||||
|
|
||||||
if len(sym) == 0 {
|
|
||||||
// mana = mana + "\U0001F6AB" //probibited sign for lands
|
|
||||||
mana = mana + "None" //probibited sign for lands
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, v := range sym {
|
|
||||||
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
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func convert_rarities(rar []primitive.M) Rarities {
|
|
||||||
|
|
||||||
// this is maybe the ugliest way someone could choose to verify, if a rarity type is missing
|
|
||||||
// [
|
|
||||||
// { _id: { rarity: 'common' }, count: 20 },
|
|
||||||
// { _id: { rarity: 'uncommon' }, count: 2 }
|
|
||||||
// ]
|
|
||||||
// if a result like this is there, 1 rarity type "rare" is not in the array. and needs to be
|
|
||||||
// initialized with 0, otherwise we get a panic
|
|
||||||
|
|
||||||
var ri Rarities
|
|
||||||
for _, r := range rar {
|
|
||||||
switch r["_id"] {
|
|
||||||
case "rare":
|
|
||||||
ri.Rares = r["count"].(float64)
|
|
||||||
case "uncommon":
|
|
||||||
ri.Uncommons = r["count"].(float64)
|
|
||||||
case "common":
|
|
||||||
ri.Commons = r["count"].(float64)
|
|
||||||
case "mythic":
|
|
||||||
ri.Mythics = r["count"].(float64)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ri
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func print_price_history(prices []PriceEntry, prefix string) {
|
|
||||||
|
|
||||||
var before float64
|
|
||||||
for _, e := range prices {
|
|
||||||
if e.Value > before && before != 0 {
|
|
||||||
fmt.Printf("%s%s%s %.2f EUR%s (%+.2f%%)\n", prefix, stringToTime(e.Date), Green, e.Value, Reset, (e.Value/before*100)-100)
|
|
||||||
} else if e.Value < before {
|
|
||||||
fmt.Printf("%s%s%s %.2f EUR%s (%+.2f%%)\n", prefix, stringToTime(e.Date), Red, e.Value, Reset, (e.Value/before*100)-100)
|
|
||||||
} else {
|
|
||||||
fmt.Printf("%s%s %.2f EUR%s\n", prefix, stringToTime(e.Date), e.Value, Reset)
|
|
||||||
}
|
|
||||||
before = e.Value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,65 +0,0 @@
|
|||||||
package serra
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"go.mongodb.org/mongo-driver/bson"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
rootCmd.AddCommand(missingCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
var missingCmd = &cobra.Command{
|
|
||||||
Aliases: []string{"m"},
|
|
||||||
Use: "missing <set>",
|
|
||||||
Short: "Display missing cards from a set",
|
|
||||||
Long: `In case you are a set collector, you can generate a list of
|
|
||||||
cards you dont own (yet) :)`,
|
|
||||||
SilenceErrors: true,
|
|
||||||
RunE: func(cmd *cobra.Command, setname []string) error {
|
|
||||||
|
|
||||||
client := storage_connect()
|
|
||||||
coll := &Collection{client.Database("serra").Collection("cards")}
|
|
||||||
defer storage_disconnect(client)
|
|
||||||
|
|
||||||
// fetch all cards in set
|
|
||||||
cards, err := coll.storage_find(bson.D{{"set", setname[0]}}, bson.D{{"collectornumber", 1}})
|
|
||||||
if (err != nil) || len(cards) == 0 {
|
|
||||||
LogMessage(fmt.Sprintf("Error: Set %s not found or no card in your collection.", setname[0]), "red")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// fetch set informations
|
|
||||||
setcoll := &Collection{client.Database("serra").Collection("sets")}
|
|
||||||
sets, _ := setcoll.storage_find_set(bson.D{{"code", setname[0]}}, bson.D{{"_id", 1}})
|
|
||||||
set := sets[0]
|
|
||||||
|
|
||||||
LogMessage(fmt.Sprintf("Missing cards in %s", sets[0].Name), "green")
|
|
||||||
|
|
||||||
// generate set with all setnumbers
|
|
||||||
var complete_set []string
|
|
||||||
var i int64
|
|
||||||
for i = 1; i <= set.CardCount; i++ {
|
|
||||||
complete_set = append(complete_set, strconv.FormatInt(i, 10))
|
|
||||||
}
|
|
||||||
|
|
||||||
// iterate over all cards in collection
|
|
||||||
var in_collection []string
|
|
||||||
for _, c := range cards {
|
|
||||||
in_collection = append(in_collection, c.CollectorNumber)
|
|
||||||
}
|
|
||||||
|
|
||||||
misses := missing(in_collection, complete_set)
|
|
||||||
for _, m := range misses {
|
|
||||||
ncard, err := fetch_card(fmt.Sprintf("%s/%s", setname[0], m))
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
fmt.Printf("%.02f\t%s (%s)\n", ncard.Prices.Eur, ncard.Name, ncard.SetName)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@ -1,47 +0,0 @@
|
|||||||
package serra
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"go.mongodb.org/mongo-driver/bson"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
removeCmd.Flags().Int64VarP(&count, "count", "c", 1, "Amount of cards to remove")
|
|
||||||
rootCmd.AddCommand(removeCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
var removeCmd = &cobra.Command{
|
|
||||||
Aliases: []string{"a"},
|
|
||||||
Use: "remove",
|
|
||||||
Short: "Remove a card from your collection",
|
|
||||||
Long: "Removes a card from your collection. Amount can be modified using flags",
|
|
||||||
SilenceErrors: true,
|
|
||||||
RunE: func(cmd *cobra.Command, cards []string) error {
|
|
||||||
|
|
||||||
client := storage_connect()
|
|
||||||
coll := &Collection{client.Database("serra").Collection("cards")}
|
|
||||||
defer storage_disconnect(client)
|
|
||||||
|
|
||||||
// Loop over different cards
|
|
||||||
for _, card := range cards {
|
|
||||||
// Fetch card from scryfall
|
|
||||||
c, err := find_card_by_setcollectornumber(coll, strings.Split(card, "/")[0], strings.Split(card, "/")[1])
|
|
||||||
if err != nil {
|
|
||||||
LogMessage(fmt.Sprintf("%v", err), "red")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.SerraCount > 1 {
|
|
||||||
modify_count_of_card(coll, c, -1)
|
|
||||||
} else {
|
|
||||||
coll.storage_remove(bson.M{"_id": c.ID})
|
|
||||||
LogMessage(fmt.Sprintf("\"%s\" (%.2f Eur) removed from the Collection.", c.Name, c.Prices.Eur), "green")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
package serra
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var Version = "unknown"
|
|
||||||
var count int64
|
|
||||||
var limit float64
|
|
||||||
var name string
|
|
||||||
var since string
|
|
||||||
var rarity, set, sort string
|
|
||||||
var unique bool
|
|
||||||
|
|
||||||
var rootCmd = &cobra.Command{
|
|
||||||
Version: Version,
|
|
||||||
Long: `serra - Magic: The Gathering Collection Tracker`,
|
|
||||||
Use: "serra",
|
|
||||||
DisableFlagsInUseLine: true,
|
|
||||||
SilenceErrors: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
func Execute() {
|
|
||||||
Banner()
|
|
||||||
if err := rootCmd.Execute(); err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,225 +0,0 @@
|
|||||||
package serra
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Card struct {
|
|
||||||
// Added by Serra
|
|
||||||
SerraCount int64 `bson:"serra_count"`
|
|
||||||
SerraPrices []PriceEntry `bson:"serra_prices"`
|
|
||||||
SerraCreated primitive.DateTime `bson:"serra_created"`
|
|
||||||
SerraUpdated primitive.DateTime `bson:"serra_updated"`
|
|
||||||
|
|
||||||
Artist string `json:"artist"`
|
|
||||||
ArtistIds []string `json:"artist_ids"`
|
|
||||||
Booster bool `json:"booster"`
|
|
||||||
BorderColor string `json:"border_color"`
|
|
||||||
CardBackID string `json:"card_back_id"`
|
|
||||||
Cmc int64 `json:"cmc"`
|
|
||||||
CollectorNumber string `json:"collector_number"`
|
|
||||||
ColorIdentity []string `json:"color_identity"`
|
|
||||||
Colors []string `json:"colors"`
|
|
||||||
Digital bool `json:"digital"`
|
|
||||||
EdhrecRank int64 `json:"edhrec_rank"`
|
|
||||||
Finishes []string `json:"finishes"`
|
|
||||||
Foil bool `json:"foil"`
|
|
||||||
Frame string `json:"frame"`
|
|
||||||
FullArt bool `json:"full_art"`
|
|
||||||
Games []string `json:"games"`
|
|
||||||
HighresImage bool `json:"highres_image"`
|
|
||||||
ID string `json:"id" bson:"_id"`
|
|
||||||
IllustrationID string `json:"illustration_id"`
|
|
||||||
ImageStatus string `json:"image_status"`
|
|
||||||
ImageUris struct {
|
|
||||||
ArtCrop string `json:"art_crop"`
|
|
||||||
BorderCrop string `json:"border_crop"`
|
|
||||||
Large string `json:"large"`
|
|
||||||
Normal string `json:"normal"`
|
|
||||||
Png string `json:"png"`
|
|
||||||
Small string `json:"small"`
|
|
||||||
} `json:"image_uris"`
|
|
||||||
Keywords []interface{} `json:"keywords"`
|
|
||||||
Lang string `json:"lang"`
|
|
||||||
Layout string `json:"layout"`
|
|
||||||
Legalities struct {
|
|
||||||
Alchemy string `json:"alchemy"`
|
|
||||||
Brawl string `json:"brawl"`
|
|
||||||
Commander string `json:"commander"`
|
|
||||||
Duel string `json:"duel"`
|
|
||||||
Future string `json:"future"`
|
|
||||||
Gladiator string `json:"gladiator"`
|
|
||||||
Historic string `json:"historic"`
|
|
||||||
Historicbrawl string `json:"historicbrawl"`
|
|
||||||
Legacy string `json:"legacy"`
|
|
||||||
Modern string `json:"modern"`
|
|
||||||
Oldschool string `json:"oldschool"`
|
|
||||||
Pauper string `json:"pauper"`
|
|
||||||
Paupercommander string `json:"paupercommander"`
|
|
||||||
Penny string `json:"penny"`
|
|
||||||
Pioneer string `json:"pioneer"`
|
|
||||||
Premodern string `json:"premodern"`
|
|
||||||
Standard string `json:"standard"`
|
|
||||||
Vintage string `json:"vintage"`
|
|
||||||
} `json:"legalities"`
|
|
||||||
ManaCost string `json:"mana_cost"`
|
|
||||||
MultiverseIds []interface{} `json:"multiverse_ids"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Nonfoil bool `json:"nonfoil"`
|
|
||||||
Object string `json:"object"`
|
|
||||||
OracleID string `json:"oracle_id"`
|
|
||||||
OracleText string `json:"oracle_text"`
|
|
||||||
Oversized bool `json:"oversized"`
|
|
||||||
Prices struct {
|
|
||||||
Eur float64 `json:"eur,string"`
|
|
||||||
EurFoil float64 `json:"eur_foil,string"`
|
|
||||||
Tix float64 `json:"tix,string"`
|
|
||||||
Usd float64 `json:"usd,string"`
|
|
||||||
UsdEtched float64 `json:"usd_etched,string"`
|
|
||||||
UsdFoil float64 `json:"usd_foil,string"`
|
|
||||||
} `json:"prices"`
|
|
||||||
PrintedName string `json:"printed_name"`
|
|
||||||
PrintedText string `json:"printed_text"`
|
|
||||||
PrintedTypeLine string `json:"printed_type_line"`
|
|
||||||
PrintsSearchURI string `json:"prints_search_uri"`
|
|
||||||
Promo bool `json:"promo"`
|
|
||||||
PurchaseUris struct {
|
|
||||||
Cardhoarder string `json:"cardhoarder"`
|
|
||||||
Cardmarket string `json:"cardmarket"`
|
|
||||||
Tcgplayer string `json:"tcgplayer"`
|
|
||||||
} `json:"purchase_uris"`
|
|
||||||
Rarity string `json:"rarity"`
|
|
||||||
RelatedUris struct {
|
|
||||||
Edhrec string `json:"edhrec"`
|
|
||||||
Mtgtop8 string `json:"mtgtop8"`
|
|
||||||
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"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type PriceEntry struct {
|
|
||||||
Date primitive.DateTime `bson:"date"`
|
|
||||||
Value float64 `bson:"value"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sets
|
|
||||||
|
|
||||||
type SetList struct {
|
|
||||||
Data []Set `json:"data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Set struct {
|
|
||||||
SerraPrices []PriceEntry `bson:"serra_prices"`
|
|
||||||
SerraCreated primitive.DateTime `bson:"serra_created"`
|
|
||||||
SerraUpdated primitive.DateTime `bson:"serra_updated"`
|
|
||||||
CardCount int64 `json:"card_count" bson:"cardcount"`
|
|
||||||
Code string `json:"code"`
|
|
||||||
Digital bool `json:"digital"`
|
|
||||||
FoilOnly bool `json:"foil_only"`
|
|
||||||
IconSvgURI string `json:"icon_svg_uri"`
|
|
||||||
ID string `json:"id" bson:"_id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
NonfoilOnly bool `json:"nonfoil_only"`
|
|
||||||
Object string `json:"object"`
|
|
||||||
ReleasedAt string `json:"released_at"`
|
|
||||||
ScryfallURI string `json:"scryfall_uri"`
|
|
||||||
SearchURI string `json:"search_uri"`
|
|
||||||
SetType string `json:"set_type"`
|
|
||||||
TcgplayerID int64 `json:"tcgplayer_id"`
|
|
||||||
URI string `json:"uri"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func fetch_card(path string) (*Card, error) {
|
|
||||||
|
|
||||||
if !strings.Contains(path, "/") {
|
|
||||||
err := errors.New(fmt.Sprintf("Card must follow format <set>/<number>, for example: ath/15"))
|
|
||||||
return &Card{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO better URL Building...
|
|
||||||
resp, err := http.Get(fmt.Sprintf("https://api.scryfall.com/cards/%s/", path))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
return &Card{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode != 200 {
|
|
||||||
err := errors.New(fmt.Sprintf("Error: %s not found", path))
|
|
||||||
return &Card{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
//We Read the response body on the line below.
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
return &Card{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
r := bytes.NewReader(body)
|
|
||||||
decoder := json.NewDecoder(r)
|
|
||||||
val := &Card{}
|
|
||||||
err = decoder.Decode(val)
|
|
||||||
|
|
||||||
// Set created Time
|
|
||||||
val.SerraCreated = primitive.NewDateTimeFromTime(time.Now())
|
|
||||||
|
|
||||||
// Increase Price
|
|
||||||
val.SerraPrices = append(val.SerraPrices, PriceEntry{primitive.NewDateTimeFromTime(time.Now()), val.Prices.Eur})
|
|
||||||
|
|
||||||
return val, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func fetch_sets() (*SetList, error) {
|
|
||||||
// TODO better URL Building...
|
|
||||||
resp, err := http.Get(fmt.Sprintf("https://api.scryfall.com/sets"))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
return &SetList{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.StatusCode != 200 {
|
|
||||||
err := errors.New(fmt.Sprintf("Error: /sets not found"))
|
|
||||||
return &SetList{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
//We Read the response body on the line below.
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
return &SetList{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
r := bytes.NewReader(body)
|
|
||||||
decoder := json.NewDecoder(r)
|
|
||||||
val := &SetList{}
|
|
||||||
err = decoder.Decode(val)
|
|
||||||
|
|
||||||
return val, nil
|
|
||||||
}
|
|
||||||
145
src/serra/set.go
145
src/serra/set.go
@ -1,145 +0,0 @@
|
|||||||
package serra
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"go.mongodb.org/mongo-driver/bson"
|
|
||||||
"go.mongodb.org/mongo-driver/mongo"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
rootCmd.AddCommand(setCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
var setCmd = &cobra.Command{
|
|
||||||
Aliases: []string{"cards"},
|
|
||||||
Use: "set [set]",
|
|
||||||
Short: "Search & show sets from your collection",
|
|
||||||
Long: `Search and show sets from your collection.
|
|
||||||
If you directly put a setcode as an argument, it will be displayed
|
|
||||||
otherwise you'll get a list of sets as a search result.`,
|
|
||||||
SilenceErrors: true,
|
|
||||||
RunE: func(cmd *cobra.Command, set []string) error {
|
|
||||||
if len(set) == 0 {
|
|
||||||
Sets()
|
|
||||||
} else {
|
|
||||||
ShowSet(set[0])
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func Sets() {
|
|
||||||
|
|
||||||
client := storage_connect()
|
|
||||||
coll := &Collection{client.Database("serra").Collection("cards")}
|
|
||||||
setscoll := &Collection{client.Database("serra").Collection("sets")}
|
|
||||||
defer storage_disconnect(client)
|
|
||||||
|
|
||||||
groupStage := bson.D{
|
|
||||||
{"$group", bson.D{
|
|
||||||
{"_id", "$setname"},
|
|
||||||
{"value", bson.D{{"$sum", bson.D{{"$multiply", bson.A{"$prices.eur", "$serra_count"}}}}}},
|
|
||||||
{"count", bson.D{{"$sum", bson.D{{"$multiply", bson.A{1.0, "$serra_count"}}}}}},
|
|
||||||
{"unique", bson.D{{"$sum", 1}}},
|
|
||||||
{"code", bson.D{{"$last", "$set"}}},
|
|
||||||
{"release", bson.D{{"$last", "$releasedat"}}},
|
|
||||||
}},
|
|
||||||
}
|
|
||||||
sortStage := bson.D{
|
|
||||||
{"$sort", bson.D{
|
|
||||||
{"release", 1},
|
|
||||||
}}}
|
|
||||||
|
|
||||||
sets, _ := coll.storage_aggregate(mongo.Pipeline{groupStage, sortStage})
|
|
||||||
for _, set := range sets {
|
|
||||||
setobj, _ := find_set_by_code(setscoll, set["code"].(string))
|
|
||||||
fmt.Printf("* %s %s%s%s (%s%s%s)\n", set["release"].(string)[0:4], Purple, set["_id"], Reset, Cyan, set["code"], Reset)
|
|
||||||
fmt.Printf(" Cards: %s%d/%d%s Total: %.0f \n", Yellow, set["unique"], setobj.CardCount, Reset, set["count"])
|
|
||||||
fmt.Printf(" Value: %s%.2f EUR%s\n", Pink, set["value"], Reset)
|
|
||||||
fmt.Println()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func ShowSet(setname string) error {
|
|
||||||
|
|
||||||
client := storage_connect()
|
|
||||||
coll := &Collection{client.Database("serra").Collection("cards")}
|
|
||||||
defer storage_disconnect(client)
|
|
||||||
|
|
||||||
// fetch all cards in set
|
|
||||||
cards, err := coll.storage_find(bson.D{{"set", setname}}, bson.D{{"prices.eur", -1}})
|
|
||||||
if (err != nil) || len(cards) == 0 {
|
|
||||||
LogMessage(fmt.Sprintf("Error: Set %s not found or no card in your collection.", setname), "red")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// fetch set informations
|
|
||||||
setcoll := &Collection{client.Database("serra").Collection("sets")}
|
|
||||||
sets, _ := setcoll.storage_find_set(bson.D{{"code", setname}}, bson.D{{"_id", 1}})
|
|
||||||
|
|
||||||
// set values
|
|
||||||
matchStage := bson.D{
|
|
||||||
{"$match", bson.D{
|
|
||||||
{"set", setname},
|
|
||||||
}},
|
|
||||||
}
|
|
||||||
groupStage := bson.D{
|
|
||||||
{"$group", bson.D{
|
|
||||||
{"_id", "$setname"},
|
|
||||||
{"value", bson.D{{"$sum", bson.D{{"$multiply", bson.A{"$prices.eur", "$serra_count"}}}}}},
|
|
||||||
{"count", bson.D{{"$sum", bson.D{{"$multiply", bson.A{1.0, "$serra_count"}}}}}},
|
|
||||||
}},
|
|
||||||
}
|
|
||||||
stats, _ := coll.storage_aggregate(mongo.Pipeline{matchStage, groupStage})
|
|
||||||
|
|
||||||
// set values
|
|
||||||
matchStage = bson.D{
|
|
||||||
{"$match", bson.D{
|
|
||||||
{"set", setname},
|
|
||||||
}},
|
|
||||||
}
|
|
||||||
groupStage = bson.D{
|
|
||||||
{"$group", bson.D{
|
|
||||||
{"_id", "$rarity"},
|
|
||||||
{"count", bson.D{{"$sum", bson.D{{"$multiply", bson.A{1.0, "$serra_count"}}}}}},
|
|
||||||
}}}
|
|
||||||
|
|
||||||
sortStage := bson.D{
|
|
||||||
{"$sort", bson.D{
|
|
||||||
{"_id", 1},
|
|
||||||
}}}
|
|
||||||
rar, _ := coll.storage_aggregate(mongo.Pipeline{matchStage, groupStage, sortStage})
|
|
||||||
|
|
||||||
ri := convert_rarities(rar)
|
|
||||||
|
|
||||||
LogMessage(fmt.Sprintf("%s", sets[0].Name), "green")
|
|
||||||
LogMessage(fmt.Sprintf("Set Cards: %d/%d", len(cards), sets[0].CardCount), "normal")
|
|
||||||
LogMessage(fmt.Sprintf("Total Cards: %.0f", stats[0]["count"]), "normal")
|
|
||||||
LogMessage(fmt.Sprintf("Total Value: %.2f EUR", stats[0]["value"]), "normal")
|
|
||||||
LogMessage(fmt.Sprintf("Released: %s", sets[0].ReleasedAt), "normal")
|
|
||||||
LogMessage(fmt.Sprintf("Mythics: %.0f", ri.Mythics), "normal")
|
|
||||||
LogMessage(fmt.Sprintf("Rares: %.0f", ri.Rares), "normal")
|
|
||||||
LogMessage(fmt.Sprintf("Uncommons: %.0f", ri.Uncommons), "normal")
|
|
||||||
LogMessage(fmt.Sprintf("Commons: %.0f", ri.Commons), "normal")
|
|
||||||
fmt.Printf("\n%sPrice History:%s\n", Pink, Reset)
|
|
||||||
print_price_history(sets[0].SerraPrices, "* ")
|
|
||||||
|
|
||||||
fmt.Printf("\n%sMost valuable cards%s\n", Pink, Reset)
|
|
||||||
ccards := 0
|
|
||||||
if len(cards) < 10 {
|
|
||||||
ccards = len(cards)
|
|
||||||
|
|
||||||
} else {
|
|
||||||
ccards = 10
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < ccards; i++ {
|
|
||||||
card := cards[i]
|
|
||||||
fmt.Printf("* %dx %s%s%s (%s/%s) %s%.2f EUR%s\n", card.SerraCount, Purple, card.Name, Reset, sets[0].Code, card.CollectorNumber, Yellow, card.Prices.Eur, Reset)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@ -1,89 +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 := storage_connect()
|
|
||||||
coll := &Collection{client.Database("serra").Collection("cards")}
|
|
||||||
totalcoll := &Collection{client.Database("serra").Collection("total")}
|
|
||||||
defer storage_disconnect(client)
|
|
||||||
|
|
||||||
sets, _ := coll.storage_aggregate(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", convert_mana_symbols(s), Purple, set["count"], Reset)
|
|
||||||
}
|
|
||||||
|
|
||||||
stats, _ := coll.storage_aggregate(mongo.Pipeline{
|
|
||||||
bson.D{
|
|
||||||
{"$group", bson.D{
|
|
||||||
{"_id", nil},
|
|
||||||
{"value", bson.D{{"$sum", bson.D{{"$multiply", bson.A{"$prices.eur", "$serra_count"}}}}}},
|
|
||||||
{"count", bson.D{{"$sum", bson.D{{"$multiply", bson.A{1.0, "$serra_count"}}}}}},
|
|
||||||
{"rarity", bson.D{{"$sum", "$rarity"}}},
|
|
||||||
{"unique", bson.D{{"$sum", 1}}},
|
|
||||||
}}},
|
|
||||||
})
|
|
||||||
fmt.Printf("\n%sCards %s\n", Green, Reset)
|
|
||||||
fmt.Printf("Total Cards: %s%.0f%s\n", Yellow, stats[0]["count"], Reset)
|
|
||||||
fmt.Printf("Unique Cards: %s%d%s\n", Purple, stats[0]["unique"], Reset)
|
|
||||||
|
|
||||||
rar, _ := coll.storage_aggregate(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 := convert_rarities(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)
|
|
||||||
|
|
||||||
fmt.Printf("\n%sTotal Value%s\n", Green, Reset)
|
|
||||||
fmt.Printf("Current: %s%.2f%s\n", Pink, stats[0]["value"], Reset)
|
|
||||||
total, _ := totalcoll.storage_find_total()
|
|
||||||
|
|
||||||
fmt.Printf("History: \n")
|
|
||||||
print_price_history(total.Value, "* ")
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@ -1,118 +0,0 @@
|
|||||||
package serra
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/schollz/progressbar/v3"
|
|
||||||
"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(updateCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
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.`,
|
|
||||||
SilenceErrors: true,
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
|
|
||||||
client := storage_connect()
|
|
||||||
defer storage_disconnect(client)
|
|
||||||
|
|
||||||
// update sets
|
|
||||||
setscoll := &Collection{client.Database("serra").Collection("sets")}
|
|
||||||
coll := &Collection{client.Database("serra").Collection("cards")}
|
|
||||||
totalcoll := &Collection{client.Database("serra").Collection("total")}
|
|
||||||
|
|
||||||
sets, _ := fetch_sets()
|
|
||||||
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.
|
|
||||||
set.SerraPrices = []PriceEntry{}
|
|
||||||
setscoll.storage_add_set(&set)
|
|
||||||
|
|
||||||
cards, _ := coll.storage_find(bson.D{{"set", set.Code}}, bson.D{{"_id", 1}})
|
|
||||||
|
|
||||||
// if no cards in collection for this set, skip it
|
|
||||||
if len(cards) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
bar := progressbar.NewOptions(len(cards),
|
|
||||||
progressbar.OptionSetWidth(50),
|
|
||||||
progressbar.OptionSetDescription(fmt.Sprintf("%s, %s%s%s\t", set.ReleasedAt[0:4], Yellow, set.Code, Reset)),
|
|
||||||
progressbar.OptionEnableColorCodes(true),
|
|
||||||
progressbar.OptionShowCount(),
|
|
||||||
progressbar.OptionSetTheme(progressbar.Theme{
|
|
||||||
Saucer: "[green]=[reset]",
|
|
||||||
SaucerHead: "[green]>[reset]",
|
|
||||||
SaucerPadding: " ",
|
|
||||||
BarStart: "|",
|
|
||||||
BarEnd: fmt.Sprintf("| %s%s%s", Pink, set.Name, Reset),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
for _, card := range cards {
|
|
||||||
bar.Add(1)
|
|
||||||
updated_card, err := fetch_card(fmt.Sprintf("%s/%s", card.Set, card.CollectorNumber))
|
|
||||||
if err != nil {
|
|
||||||
LogMessage(fmt.Sprintf("%v", err), "red")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
update := bson.M{
|
|
||||||
"$set": bson.M{"serra_updated": primitive.NewDateTimeFromTime(time.Now()), "prices": updated_card.Prices, "collectornumber": updated_card.CollectorNumber},
|
|
||||||
"$push": bson.M{"serra_prices": bson.M{"date": primitive.NewDateTimeFromTime(time.Now()),
|
|
||||||
"value": updated_card.Prices.Eur}},
|
|
||||||
}
|
|
||||||
coll.storage_update(bson.M{"_id": bson.M{"$eq": card.ID}}, update)
|
|
||||||
}
|
|
||||||
fmt.Println()
|
|
||||||
|
|
||||||
// update set value sum
|
|
||||||
|
|
||||||
// calculate value summary
|
|
||||||
matchStage := bson.D{
|
|
||||||
{"$match", bson.D{
|
|
||||||
{"set", set.Code},
|
|
||||||
}},
|
|
||||||
}
|
|
||||||
groupStage := bson.D{
|
|
||||||
{"$group", bson.D{
|
|
||||||
{"_id", "$set"},
|
|
||||||
{"value", bson.D{{"$sum", bson.D{{"$multiply", bson.A{"$prices.eur", "$serra_count"}}}}}},
|
|
||||||
}}}
|
|
||||||
setvalue, _ := coll.storage_aggregate(mongo.Pipeline{matchStage, groupStage})
|
|
||||||
|
|
||||||
// do the update
|
|
||||||
set_update := bson.M{
|
|
||||||
"$set": bson.M{"serra_updated": primitive.NewDateTimeFromTime(time.Now())},
|
|
||||||
"$push": bson.M{"serra_prices": bson.M{"date": primitive.NewDateTimeFromTime(time.Now()),
|
|
||||||
"value": setvalue[0]["value"]}},
|
|
||||||
}
|
|
||||||
// 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.storage_update(bson.M{"code": bson.M{"$eq": set.Code}}, set_update)
|
|
||||||
}
|
|
||||||
|
|
||||||
// calculate total summary over all sets
|
|
||||||
overall_value := mongo.Pipeline{
|
|
||||||
bson.D{{"$match",
|
|
||||||
bson.D{{"serra_prices", bson.D{{"$type", "array"}}}}}},
|
|
||||||
bson.D{{"$project",
|
|
||||||
bson.D{{"name", true}, {"totalValue", bson.D{{"$arrayElemAt", bson.A{"$serra_prices", -1}}}}}}},
|
|
||||||
bson.D{{"$group", bson.D{{"_id", nil}, {"total", bson.D{{"$sum", "$totalValue.value"}}}}}},
|
|
||||||
}
|
|
||||||
ostats, _ := setscoll.storage_aggregate(overall_value)
|
|
||||||
fmt.Printf("\n%sUpdating total value of collection to: %s%.02f EUR%s\n", Green, Yellow, ostats[0]["total"].(float64), Reset)
|
|
||||||
totalcoll.storage_add_total(ostats[0]["total"].(float64))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@ -1,36 +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"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Banner() {
|
|
||||||
LogMessage(fmt.Sprintf("%s Serra %v\n", Icon, Version), "green")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
274
templates/index.tmpl
Normal file
274
templates/index.tmpl
Normal file
@ -0,0 +1,274 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<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"> -->
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.cardpreview {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
border-bottom: 1px dotted black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cardpreview .cardpreviewtext {
|
||||||
|
visibility: hidden;
|
||||||
|
display: none;
|
||||||
|
width: 300px;
|
||||||
|
color: #fff;
|
||||||
|
|
||||||
|
/* Position the cardpreview */
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cardpreview:hover .cardpreviewtext {
|
||||||
|
visibility: visible;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 900px) {
|
||||||
|
.level-item {
|
||||||
|
justify-content: unset
|
||||||
|
}
|
||||||
|
|
||||||
|
table#cards td,
|
||||||
|
table#cards th {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
table#cards th:nth-child(1),
|
||||||
|
table#cards th:nth-child(2),
|
||||||
|
table#cards th:nth-child(4),
|
||||||
|
table#cards th:nth-child(5),
|
||||||
|
table#cards th:nth-child(9) {
|
||||||
|
display: revert;
|
||||||
|
}
|
||||||
|
|
||||||
|
table#cards td:nth-child(1),
|
||||||
|
table#cards td:nth-child(2),
|
||||||
|
table#cards td:nth-child(4),
|
||||||
|
table#cards td:nth-child(5),
|
||||||
|
table#cards td:nth-child(9) {
|
||||||
|
display: revert;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<!-- Site Title -->
|
||||||
|
<section class="hero is-black">
|
||||||
|
<div class="hero-body">
|
||||||
|
<p class="title">
|
||||||
|
<a href="/">{{ .title }}</a>
|
||||||
|
</p>
|
||||||
|
<p class="subtitle">
|
||||||
|
<i>Magic: The Gathering</i> Collection
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Nav Bar -->
|
||||||
|
<section class="section">
|
||||||
|
<nav class="level">
|
||||||
|
|
||||||
|
<!-- Left side -->
|
||||||
|
<div class="level-left">
|
||||||
|
<div class="level-item">
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">Set</label>
|
||||||
|
<div class="control">
|
||||||
|
<div class="select">
|
||||||
|
<select name="set" id="set" form="searchform">
|
||||||
|
<option value="">-</option>
|
||||||
|
{{range .sets}}
|
||||||
|
<option value="{{ index . "code" }}">{{ printf "%.25s" (index . "_id") }} ({{ index . "count" }})
|
||||||
|
</option>
|
||||||
|
{{end}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="level-item">
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">Name</label>
|
||||||
|
<div class="control">
|
||||||
|
<input form="searchform" name="name" class="input" id="name" type="text" placeholder="Tolarian Academy">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input type="hidden" id="limit" name="limit" value="500" form="searchform">
|
||||||
|
<input type="hidden" id="page" name="page" value="0" form="searchform">
|
||||||
|
|
||||||
|
<div class="level-item">
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">Sort</label>
|
||||||
|
<div class="control">
|
||||||
|
<div class="select">
|
||||||
|
<select name="sort" id="sort" form="searchform">
|
||||||
|
<option value="name" selected>Name</option>
|
||||||
|
<option value="value">Value</option>
|
||||||
|
<option value="number">Collector Number</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Right side -->
|
||||||
|
<div class="level-right">
|
||||||
|
<form action="/" id="searchform">
|
||||||
|
<input class="button is-primary" type="submit" value="Search">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- Results Table -->
|
||||||
|
<table id="cards" class="table is-fullwidth">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th><abbr title="Amount">#</abbr></th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th><abbr title="Set Code">Set</abbr></th>
|
||||||
|
<th><abbr title="Collector Number">C</abbr></th>
|
||||||
|
<th><abbr title="Rarity">Rar</abbr></th>
|
||||||
|
<th><abbr title="Price">$</abbr></th>
|
||||||
|
<th><abbr title="Price Foil">$*</abbr></th>
|
||||||
|
<th><abbr title="Price Eur">€</abbr></th>
|
||||||
|
<th><abbr title="Price Eur Foil">€*</abbr></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tfoot>
|
||||||
|
<tr>
|
||||||
|
<th><abbr title="Amount">#</abbr></th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th><abbr title="Set Code">Set</abbr></th>
|
||||||
|
<th><abbr title="Collector Number">C</abbr></th>
|
||||||
|
<th><abbr title="Rarity">Rar</abbr></th>
|
||||||
|
<th><abbr title="Price">$</abbr></th>
|
||||||
|
<th><abbr title="Price Foil">$*</abbr></th>
|
||||||
|
<th><abbr title="Price Eur">€</abbr></th>
|
||||||
|
<th><abbr title="Price Eur Foil">€*</abbr></th>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
<tbody>
|
||||||
|
{{range .cards}}
|
||||||
|
<tr>
|
||||||
|
<td>{{ add .SerraCount .SerraCountFoil }}</td>
|
||||||
|
<td>
|
||||||
|
<div class="cardpreview"><strong>{{.Name }}</strong>
|
||||||
|
<span class="cardpreviewtext">
|
||||||
|
<img loading="lazy" src="{{ .ImageUris.Normal }}" alt="" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>{{.TypeLine}}</td>
|
||||||
|
<td>{{.Set}}</td>
|
||||||
|
<td>{{.CollectorNumber}}</td>
|
||||||
|
<td>{{.Rarity}}</td>
|
||||||
|
<td>{{.Prices.Usd}}</td>
|
||||||
|
<td>{{.Prices.UsdFoil}}</td>
|
||||||
|
<td>{{.Prices.Eur}}</td>
|
||||||
|
<td>{{.Prices.EurFoil}}</td>
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{{ if ne .numPages 0 }}
|
||||||
|
<div class="hero-body">
|
||||||
|
<nav class="pagination" role="navigation" aria-label="pagination">
|
||||||
|
|
||||||
|
{{ if ge .prevPage 0 }}
|
||||||
|
<a href="/?set={{.query.Set}}&name={{.query.Name}}&sort={{.query.Sort}}&limit={{.limit}}&page={{.prevPage}}" class="pagination-previous">Previous</a>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ if ( le .nextPage .numPages) }}
|
||||||
|
<a href="/?set={{.query.Set}}&name={{.query.Name}}&sort={{.query.Sort}}&limit={{.limit}}&page={{.nextPage}}" class="pagination-next">Next page</a>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
<ul class="pagination-list">
|
||||||
|
|
||||||
|
{{ if ne .page 0 }}
|
||||||
|
<li>
|
||||||
|
<a class="pagination-link" href="/?set={{.query.Set}}&name={{.query.Name}}&sort={{.query.Sort}}&limit={{.limit}}&page=0" aria-label="Goto page 0">0</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<span class="pagination-ellipsis">…</span>
|
||||||
|
</li>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{ if gt .prevPage 0 }}
|
||||||
|
<li>
|
||||||
|
<a href="/?set={{.query.Set}}&name={{.query.Name}}&sort={{.query.Sort}}&limit={{.limit}}&page={{.prevPage}}" class="pagination-link" aria-label="Goto page {{.prevPage}}">{{.prevPage}}</a>
|
||||||
|
</li>
|
||||||
|
{{end}}
|
||||||
|
<li>
|
||||||
|
<a class="pagination-link is-current" href="/?set={{.query.Set}}&name={{.query.Name}}&sort={{.query.Sort}}&limit={{.limit}}&page={{.page}}" aria-label="Page {{ .page }}" aria-current="page">{{.page}}</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
{{ if and (ne .nextPage .numPages) ( lt .nextPage .numPages) }}
|
||||||
|
<li>
|
||||||
|
<a href="/?set={{.query.Set}}&name={{.query.Name}}&sort={{.query.Sort}}&limit={{.limit}}&page={{.nextPage}}" class="pagination-link" aria-label="Goto page {{.nextPage}} ">{{.nextPage}}</a>
|
||||||
|
</li>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ if ne .numPages .page }}
|
||||||
|
<li>
|
||||||
|
<span class="pagination-ellipsis">…</span>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="pagination-link" href="/?set={{.query.Set}}&name={{.query.Name}}&sort={{.query.Sort}}&limit={{.limit}}&page={{.numPages}}" aria-label="Goto page {{.numPages}}">{{.numPages}}</a>
|
||||||
|
</li>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
<!-- Select set from last search -->
|
||||||
|
<script>
|
||||||
|
function getParam(paramName) {
|
||||||
|
return decodeURI(
|
||||||
|
(RegExp(paramName + '=' + '(.+?)(&|$)').exec(location.search) || [, 500])[1]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
var selectedSetVal = getParam("set");
|
||||||
|
document.getElementById("set").value = selectedSetVal;
|
||||||
|
|
||||||
|
var selectedLimitVal = getParam("limit");
|
||||||
|
document.getElementById("limit").value = selectedLimitVal;
|
||||||
|
|
||||||
|
var selectedSortVal = getParam("sort");
|
||||||
|
document.getElementById("sort").value = selectedSortVal;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<footer class="footer">
|
||||||
|
<div class="content has-text-centered">
|
||||||
|
<p>
|
||||||
|
<strong><a href="https://github.com/noqqe/serra">Serra</a></strong> Version {{ .version }} by <a href="https://noqqe.de">noqqe</a>.
|
||||||
|
<a href="http://opensource.org/licenses/mit-license.php">MIT</a>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
Loading…
x
Reference in New Issue
Block a user