Compare commits

..

22 Commits

Author SHA1 Message Date
The Magician d2bc986506 Add new Make targets for goose 2024-11-25 15:03:16 +00:00
The Magician 78e1c865c7 Fix goose subcommand 2024-11-25 14:58:47 +00:00
The Magician e6466cc01a Add file to create new database migration 2024-11-25 14:57:35 +00:00
The Magician 2fdbb0d467 Add tools for interacting with scanner 2024-11-24 17:47:43 +00:00
The Magician 9d6d39d75f Add script to query Cardtrader API 2024-11-24 17:47:00 +00:00
The Magician 2ab69dce8e Add scripts to add or remove items from storage 2024-11-24 17:24:57 +00:00
The Magician 7fd6ed07f3 Add "add" and "product" subcommands 2024-11-24 17:24:42 +00:00
The Magician 93184d4c52 Add CardtraderProductId field to CardLocation 2024-11-24 17:18:27 +00:00
The Magician 1b579a28da Add Cardtrader API package 2024-11-24 17:16:37 +00:00
The Magician b30525c2d4 Fix search-storage command and add remove command 2024-10-29 22:07:06 +00:00
The Magician 0dd67f3bb8 Add search-printings command 2024-10-28 19:47:23 +00:00
The Magician 2ef339bbce Add "store" command to add cards individually 2024-10-10 15:43:56 +01:00
The Magician 6a4ff5dffd Update card_scanner 2024-10-10 10:56:35 +01:00
The Magician ec5328fed1 Remove scantap directory 2024-10-09 23:15:40 +01:00
The Magician b90a570d29 Rename scripts 2024-10-09 23:14:55 +01:00
The Magician 0e1647bb2d Add CardScan table and CardPrinting for scanned but unidentified cards 2024-10-09 23:12:56 +01:00
The Magician a028126fba Add outline for `deck` command 2024-10-09 20:33:47 +01:00
The Magician 5e271c4132 Remove old scanner programs 2024-10-09 20:32:30 +01:00
The Magician 03e8dc8c03 Add script to automatically import scan file from phone 2024-10-09 20:31:45 +01:00
The Magician c1f581f312 Add storagearea subcommand 2024-10-04 21:51:05 +01:00
The Magician f7e0a713f6 Implement search command 2024-10-04 13:44:22 +01:00
The Magician c0c290916a Move update logic to subcommand 2024-10-04 12:06:59 +01:00
49 changed files with 726 additions and 686 deletions

View File

@ -1,5 +0,0 @@
cmake_install.cmake
CMakeCache.txt
CMakeFiles/*
Makefile
AScannerDarkly

View File

@ -1,7 +0,0 @@
cmake_minimum_required(VERSION 3.2)
project( AScannerDarkly )
find_package( OpenCV REQUIRED )
include_directories( ${OpenCV_INCLUDE_DIRS} )
add_executable( AScannerDarkly main.cpp )
target_link_libraries( AScannerDarkly ${OpenCV_LIBS} )
target_link_libraries( AScannerDarkly -llept -ltesseract )

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -1,161 +0,0 @@
#include <opencv2/opencv.hpp>
#include <tesseract/baseapi.h>
#include <leptonica/allheaders.h>
#include <string>
const std::string SCANNER_WINDOW_NAME = "A Scanner Darkly";
const std::string CARD_WINDOW_NAME = "Detected Card";
const std::string CANNY_LOWER_THRESHOLD_TRACKBAR_NAME = "Canny: Lower Threshold";
const float CARD_ASPECT_RATIO = 88.0f / 63.0f;
const std::string ASPECT_RATIO_TOLERANCE_TRACKBAR_NAME = "Aspect Ratio Tolerance";
int g_aspect_ratio_tolerance = 1;
const int MAX_ASPECT_RATIO_TOLERANCE = 100;
int g_Canny_lower_threshold = 195;
const int CANNY_UPPER_THRESHOLD = 255;
cv::Mat cropImageToRoi(cv::Mat img, cv::Rect roi) {
return img(roi);
}
cv::Mat detectCardInFrame(cv::Mat frame) {
cv::Mat grayscaleFrame, blurFrame, cannyFrame, cardFrame;
// Perform edge detection
cv::cvtColor(frame, grayscaleFrame, cv::COLOR_BGR2GRAY);
cv::GaussianBlur(grayscaleFrame, blurFrame, cv::Size(5, 5), 2, 2);
cv::Canny(blurFrame, cannyFrame, g_Canny_lower_threshold, CANNY_UPPER_THRESHOLD);
// Perform contour detection
std::vector<std::vector<cv::Point>> contours;
std::vector<cv::Vec4i> hierarchy;
cv::findContours(cannyFrame, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);
// Detect convex hulls
std::vector<std::vector<cv::Point>> hull(contours.size());
for(size_t i = 0; i < contours.size(); i++) {
cv::convexHull(contours[i], hull[i]);
}
// Detect RotatedRects
std::vector<cv::RotatedRect> minRect(contours.size());
for(size_t i = 0; i < contours.size(); i++) {
minRect[i] = cv::minAreaRect(contours[i]);
}
// Draw lines around RotatedRects
/*
cv::Scalar rectangleColor(255, 0, 0, 0);
cv::Point2f rect_points[4];
minRect[i].points(rect_points);
for (int j = 0; j < 4; j++) {
cv::line(frame, rect_points[j], rect_points[(j + 1) % 4], rectangleColor);
}
*/
// Find possible cards
std::vector<cv::RotatedRect> possibleCards(contours.size());
int possibleCount = 0;
for (size_t i = 0; i < minRect.size(); i++) {
float aspectRatio = minRect[i].size.height / minRect[i].size.width;
float aspectRatioTolerance = g_aspect_ratio_tolerance / 100.0f;
if (aspectRatio < (CARD_ASPECT_RATIO - aspectRatioTolerance) || aspectRatio > (CARD_ASPECT_RATIO + aspectRatioTolerance)) {
continue;
}
cv::putText(frame, std::to_string(minRect[i].angle), minRect[i].center, cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(255, 0, 0, 0));
possibleCards[possibleCount] = minRect[i];
possibleCount++;
}
if (possibleCount == 1) {
return cropImageToRoi(frame, possibleCards[0].boundingRect());
} else if (possibleCount > 1) {
// If we have more than one possible match, take the largest one
int largestCardIndex = 0;
int largestCardSize = 0;
for (size_t i = 0; i < possibleCards.size(); i++) {
float size = possibleCards[i].size.width * possibleCards[i].size.height;
if (size >= largestCardSize) {
largestCardIndex = i;
largestCardSize = size;
}
}
// Some basic error checking to ensure the RotatedRect is roughly card-sized
if (largestCardSize >= 14400 && largestCardSize <= 200000) {
return cropImageToRoi(frame, possibleCards[largestCardIndex].boundingRect());
}
}
return cv::Mat();
}
const char* readTextFromImage(cv::InputArray img) {
const char* tempFilename = "temp.bmp";
// Initialize Tesseract api
tesseract::TessBaseAPI* tess = new tesseract::TessBaseAPI();
tess->Init(NULL, "eng");
tess->SetPageSegMode(tesseract::PSM_SPARSE_TEXT);
// Load image into Tesseract
cv::imwrite(tempFilename, img);
Pix* pixd = pixRead(tempFilename);
tess->SetImage(pixd);
tess->Recognize(0);
// Perform OCR
const char* out = tess->GetUTF8Text();
// Cleanup
pixDestroy(&pixd);
std::remove(tempFilename);
return out;
}
int main(int argc, char** argv ) {
cv::VideoCapture cap;
cap.open(0);
if (!cap.isOpened()) {
std::cerr << "Couldn't open capture" << std::endl;
return -1;
}
cv::namedWindow(SCANNER_WINDOW_NAME);
cv::createTrackbar(CANNY_LOWER_THRESHOLD_TRACKBAR_NAME, SCANNER_WINDOW_NAME, &g_Canny_lower_threshold, CANNY_UPPER_THRESHOLD, NULL);
cv::createTrackbar(ASPECT_RATIO_TOLERANCE_TRACKBAR_NAME, SCANNER_WINDOW_NAME, &g_aspect_ratio_tolerance, MAX_ASPECT_RATIO_TOLERANCE, NULL);
cv::Mat frame, cardFrame;
while (true) {
cap >> frame;
//cardFrame = detectCardInFrame(frame);
cv::imshow(SCANNER_WINDOW_NAME, frame);
/*
if (cardFrame.size().width > 0) {
printf("Card detected\n");
while (true) {
cv::imshow(CARD_WINDOW_NAME, cardFrame);
char c = (char)cv::waitKey(33);
if (c == 27) {
cv::destroyWindow(CARD_WINDOW_NAME);
break;
}
}
}
*/
char c = (char)cv::waitKey(33);
if (c == 27) {
break;
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 301 KiB

View File

@ -1,24 +0,0 @@
#!/usr/bin/python3
import cv2
import numpy as np
mouse_raw = (0, 0)
def clickEvent(event, x, y, flags, params):
global mouse_raw
if event == cv2.EVENT_LBUTTONDOWN:
mouse_raw = (x, y)
print(mouse_raw)
cap = cv2.VideoCapture(0)
cv2.namedWindow("image")
cv2.setMouseCallback("image", clickEvent)
while True:
success, img = cap.read()
cv2.imshow("image", img)
cv2.waitKey(0)
cv2.destroyAllWindows()

59
scanner/card_scanner Executable file
View File

@ -0,0 +1,59 @@
#!/bin/bash
check_error() {
if test $? -ne 0; then
echo "card_scanner: $1 exited with unexpected error"
exit 1
fi
}
STORAGE_DIR="$HOME/.local/share/sevenkeys/scanimages/"
mkdir -p "$STORAGE_DIR"
printf "Database profile: "
read profile
printf "Storage area name: "
read storageAreaName
ADD_CARDS=0
echo "scantap: Beginning scan loop"
while true; do
rng="$(cat /dev/random | tr -cd 'a-f0-9' | head -c 32)"
filename="$STORAGE_DIR/$rng.png"
scanimage --output-file="$filename" --source "ADF Front" --mode Color --page-width 63mm --page-height 88mm 2>/dev/null
# scanimage exits with code 7 if no documents are available in scanner
if test $? -eq 7; then
if test $ADD_CARDS -eq 0; then
ADD_CARDS=1
echo "scantap: No more cards in feeder" >&2
fi
# If we have generated a zero-length file, then delete it
if ! test -s "$filename"; then
rm "$filename"
fi
continue
fi
ADD_CARDS=0
check_error "scanimage"
convert -rotate 180 "$filename" "$filename"
check_error "convert"
cardLocationId="$(./sevenkeys --profile="$profile" store --storagearea="$storageAreaName" --id="00000000-0000-0000-0000-0000000000000")"
check_error "sevenkeys"
if test "$profile" == "development"; then
databaseName="sevenkeys_development"
else
databaseName="sevenkeys"
fi
mysql --silent --silent --user=root --password="$(pass show sevenkeys/mysql)" \
-e "USE $databaseName; INSERT INTO CardScan (CardLocationId, Filename) VALUES ('$cardLocationId', '$rng.png');"
check_error "mysql"
done

8
scanner/crop Executable file
View File

@ -0,0 +1,8 @@
#!/bin/bash
crop() {
convert "$1" -crop 1200x120+90+100 "$2"
}
crop old_border.png name_old.png
crop new_border.png name_new.png

49
scanner/profiler Executable file
View File

@ -0,0 +1,49 @@
#!/bin/bash
CLASSNAME="svnProfiler"
TITLE_FILENAME="/tmp/title.png"
touch "$TITLE_FILENAME"
get_window_id() {
while true; do
id="$(xdotool search --classname "$CLASSNAME")"
if test "$?" -ne 0; then
continue
fi
echo "$id"
break
done
}
printf "Database profile: "
read profile
if test "$profile" == "development"; then
databaseName="sevenkeys_development"
else
databaseName="sevenkeys"
fi
STORAGE_DIR="$HOME/.local/share/sevenkeys/scanimages/"
cd "$STORAGE_DIR"
files="$(find *.png)"
nsxiv -N "$CLASSNAME" $files &
nsxivWindowId="$(get_window_id)"
nsxiv "$TITLE_FILENAME" &
for file in $files; do
cardLocationId="$(mysql --silent --silent --user=root --password="$(pass show sevenkeys/mysql)" -e "USE $databaseName; SELECT CardLocationId FROM CardScan WHERE Filename = '$file';")"
# TODO: Detect features of the card automatically
#convert "$file" -crop 1200x120+90+100 "$TITLE_FILENAME"
#title="$(tesseract --psm 9 "$TITLE_FILENAME" stdout)"
#echo "$title"
cardPrintingId="$(sevenkeys --profile="$profile" search-printings)"
sevenkeys --profile="$profile" replace --card-location-id="$cardLocationId" --card-printing-id="$cardPrintingId"
xdotool key --window "$nsxivWindowId" 'n'
done

9
scanner/psm_test Executable file
View File

@ -0,0 +1,9 @@
#!/bin/bash
for i in $(seq 1 13); do
echo "PSM $i:" >>output
echo "Old card:" >>output
tesseract name_old.png stdout --psm "$i" >>output
echo "New card:" >>output
tesseract name_new.png stdout --psm "$i" >>output
echo >>output
done

34
scanner/test_card_scanner_count Executable file
View File

@ -0,0 +1,34 @@
#!/bin/bash
if test -z "$1"; then
echo "usage: test_card_scanner_count <count>" >&2
exit 1
fi
STORAGE_DIR="$HOME/.local/share/sevenkeys/testimages/"
mkdir -p "$STORAGE_DIR"
starting_file_count="$(find $STORAGE_DIR/*.png | wc -l)"
echo "test_card_scanner_count: Beginning test"
while true; do
rng="$(cat /dev/random | tr -cd 'a-f0-9' | head -c 32)"
filename="$STORAGE_DIR/$rng.png"
scanimage --output-file="$filename" --source "ADF Front" --mode Color --page-width 63mm --page-height 88mm 2>/dev/null
# scanimage exits with code 7 if no documents are available in scanner
if test $? -eq 7; then
echo "test_card_scanner_count: No more cards in feeder" >&2
rm "$filename"
break
fi
done
ending_file_count="$(find $STORAGE_DIR/*.png | wc -l)"
if test $(( ending_file_count - starting_file_count )) -ne "$1"; then
echo "FAILED: Start: $starting_file_count, End: $ending_file_count"
else
echo "SUCCESS"
fi

12
scanner/test_scanner_loop Executable file
View File

@ -0,0 +1,12 @@
#!/bin/bash
STORAGE_DIR="$HOME/.local/share/sevenkeys/testimages/"
mkdir -p "$STORAGE_DIR"
echo "test_card_scanner_count: Beginning test"
while true; do
rng="$(cat /dev/random | tr -cd 'a-f0-9' | head -c 32)"
filename="$STORAGE_DIR/$rng.png"
scanimage --output-file="$filename" --source "ADF Front" --mode Color --page-width 63mm --page-height 88mm 2>/dev/null
done

View File

@ -1,8 +0,0 @@
USE sevenkeys;
CREATE TABLE IF NOT EXISTS CardScan (
Id INT PRIMARY KEY AUTO_INCREMENT,
StorageAreaId INT NOT NULL,
FOREIGN KEY (StorageAreaId) REFERENCES StorageArea(Id),
Position INT NOT NULL
);

View File

@ -1,59 +0,0 @@
#!/bin/bash
mysql --user=root --password="$(pass show sevenkeys/mysql)" <createtable.sql
printf "scantap: Enter name of storage box: "
read storageAreaName
storageAreaId="$(mysql --silent --silent --user=root --password="$(pass show sevenkeys/mysql)" -e "USE sevenkeys; SELECT Id FROM StorageArea WHERE Name = '$storageAreaName';")"
if test -z "$storageAreaId"; then
echo "scantap: No storage area named \"$storageAreaName\"" >&2
exit 1
fi
startPosition="$(mysql --silent --silent --user=root --password="$(pass show sevenkeys/mysql)" -e "USE sevenkeys; SELECT Position FROM CardScan WHERE StorageAreaId = '$storageAreaId' ORDER BY Position DESC LIMIT 1;")"
if test "$?" -ne "0"; then
echo "scantap: Failed to establish starting position" >&2
exit 1
fi
position=$((startPosition + 1))
printf "scantap: Enter mode (foil/nonfoil): "
read foilMode
outputDir="images/$foilMode"
mkdir -p "$outputDir"
ADD_CARDS=0
echo "scantap: Beginning scan loop"
while true; do
scanimage --format=png --batch=tmp%d.png --batch-count=2 --source "ADF Duplex" --mode Color --page-width 63mm --page-height 88mm 2>/dev/null
# scanimage exits with code 7 if no documents are available in scanner
if test $? -eq 7; then
if test $ADD_CARDS -eq 0; then
ADD_CARDS=1
echo "scantap: No more cards in feeder" >&2
fi
continue
fi
ADD_CARDS=0
if ! test -e tmp1.png || ! test -e tmp2.png; then
echo "scantap: Failed to create temporary image files" >&2
exit 1
fi
key="$(mysql --silent --silent --user=root --password="$(pass show sevenkeys/mysql)" -e "USE sevenkeys; INSERT INTO CardScan (StorageAreaId, Position) VALUES ('$storageAreaId', '$position'); SELECT Id FROM CardScan ORDER BY Id DESC LIMIT 1;")"
if test "$?" -ne "0"; then
echo "scantap: Failed to get key from database" >&2
exit 1
fi
position=$((position + 1))
convert -rotate 180 tmp1.png tmp1.png
convert -rotate 180 tmp2.png tmp2.png
mv tmp1.png "$outputDir/${key}_back.png"
mv tmp2.png "$outputDir/${key}_front.png"
done

View File

@ -1,9 +1,17 @@
createdb:
mysql --user=root --password=$(shell pass show sevenkeys/mysql) <database/sql/createdb.sql
removedb:
rm -rf cache/
mysql --user=root --password=$(shell pass show sevenkeys/mysql) <database/sql/removedb.sql
connect: connect:
mysql --user=root --password=$(shell pass show sevenkeys/mysql) mysql --user=root --password=$(shell pass show sevenkeys/mysql)
dump: dump:
mysqldump --user=root --password=$(shell pass show sevenkeys/mysql) sevenkeys >sevenkeys.sql mysqldump --user=root --password=$(shell pass show sevenkeys/mysql) sevenkeys >sevenkeys.sql
dev_create:
goose mysql "root:$(shell pass show sevenkeys/mysql)@/sevenkeys_development?parseTime=true" up
dev_rollback:
rm -rf cache/
goose mysql "root:$(shell pass show sevenkeys/mysql)@/sevenkeys_development?parseTime=true" reset
prod_create:
goose mysql "root:$(shell pass show sevenkeys/mysql)@/sevenkeys?parseTime=true" up
prod_rollback:
rm -rf cache/
goose mysql "root:$(shell pass show sevenkeys/mysql)@/sevenkeys?parseTime=true" reset

3
sevenkeys/add_to_storage Executable file
View File

@ -0,0 +1,3 @@
#!/bin/sh
sevenkeys --profile=production add --card-printing-id="$(sevenkeys --profile=production search-printings)" --storagearea="$1"

7
sevenkeys/cardtrader Executable file
View File

@ -0,0 +1,7 @@
#!/bin/sh
# Note: Make sure to export BEARER before using
URL="https://api.cardtrader.com/api/v2"
curl --silent "$URL/$1" -H "Authorization: Bearer $BEARER" | jq .

View File

@ -1,149 +0,0 @@
package cli
import (
"database/sql"
"errors"
"fmt"
"os/exec"
"sevenkeys/database"
"sevenkeys/delverlens"
"sevenkeys/logic"
)
var output string
func MainCliLoop(db *sql.DB) {
var command string
//var selectedStorageAreaName string
var cardLocation database.CardLocation
var insertSearchCriteria logic.SearchCriteria = logic.SearchCriteria{
SetCode: "",
Foil: logic.Either,
Promo: logic.Either,
Language: "en",
}
var insertSearchOptions logic.InsertSearchOptions
// TODO: Add the ability to modify this
var locateSearchCriteria logic.SearchCriteria = logic.SearchCriteria{
SetCode: "",
Foil: logic.True,
Promo: logic.Either,
Language: "en",
}
for {
ShowSplashScreen()
// TODO: Fix this
//showStorageInfo(os.Stdout, selectedStorageAreaName)
showInsertSearchCriteria(insertSearchCriteria)
showSelectedCard()
showCopiesInserted()
command = GetStringResponse("SEVENKEYS $")
var err error
switch command {
case "q", "quit":
return
case "n", "newstorage":
var storageArea database.StorageArea
storageArea.Name = GetStringResponse("Storage area name:")
storageArea.StorageType = GetStringResponse("Storage area type (Binder/Box):")
err = logic.CreateStorageArea(db, storageArea)
logic.Check(err)
break
case "a", "area":
options, err := logic.GetStorageAreaSearchOptions(db)
logic.Check(err)
id, _, err := logic.GenericSearch(options)
logic.Check(err)
//selectedStorageAreaName = name
// TODO: Make db call to cache StorageArea once we have the ID
cardLocation.StorageAreaId = id
break
case "c", "criteria":
insertSearchCriteria = getSearchCriteria()
break
case "s", "search":
if shouldRefreshSearch {
insertSearchOptions = getSearchOptions(db, insertSearchCriteria)
}
var previousCardPrintingId = cardLocation.CardPrintingId
pk, searchLine, err := logic.GenericSearch(insertSearchOptions)
cardLocation.CardPrintingId = pk
selectedCardPrintingSearchLine = searchLine
var exitError *exec.ExitError
if errors.As(err, &exitError) {
break
}
logic.Check(err)
output = ""
if cardLocation.CardPrintingId != previousCardPrintingId {
copiesInserted = 0
}
break
case "i", "insert":
err := logic.StoreCard(db, cardLocation)
logic.Check(err)
copiesInserted++
break
case "d", "delverlens":
filename := GetStringResponse("Filename:")
cards, err := delverlens.ParseExportFile(filename)
logic.Check(err)
err = logic.ImportDelverLensCards(db, cards, cardLocation.StorageAreaId)
logic.Check(err)
break
case "l", "locate":
filename := GetStringResponse("Filename:")
cardNames, err := logic.GetCardNamesFromFile(filename)
logic.Check(err)
locations, err := logic.LocateCards(db, cardNames, locateSearchCriteria)
logic.Check(err)
if len(locations) == 0 {
fmt.Println("No results found")
fmt.Scanln()
break
}
for _, location := range locations {
description := logic.GetLocationDescription(location)
fmt.Println(description)
for true {
todo := GetStringResponse("TODO:")
if todo == "r" {
logic.RemoveFromStorage(db, location)
break
} else if todo == "n" {
break
} else {
fmt.Printf("Unrecognized option: %s\n", todo)
}
}
}
fmt.Println("Though this query has ended, its relics still slumber in New Argive.")
fmt.Scanln()
break
default:
fmt.Println("Unrecognized command:", command)
break
}
}
}

View File

@ -1,38 +0,0 @@
package cli
import (
"bufio"
"fmt"
"os"
"sevenkeys/logic"
"strings"
)
func GetStringResponse(prompt string) string {
fmt.Print(prompt + " ")
var response string
scanner := bufio.NewScanner(os.Stdin)
scanner.Scan()
response = scanner.Text()
return response
}
func GetYesNoResponse(prompt string) bool {
response := GetStringResponse(prompt)
return strings.ToUpper(response) == "Y"
}
func GetTriadicResponse(prompt string) logic.Triadic {
response := GetStringResponse(prompt)
switch strings.ToUpper(response) {
case "Y":
return logic.True
case "N":
return logic.False
default:
return logic.Either
}
}

View File

@ -1,61 +0,0 @@
package cli
import (
"database/sql"
"fmt"
"sevenkeys/logic"
)
var shouldRefreshSearch bool = true
func getSearchCriteria() logic.SearchCriteria {
var searchCriteria logic.SearchCriteria
searchCriteria.SetCode = GetStringResponse("Set code:")
searchCriteria.Foil = GetTriadicResponse("Foil (y/n/E):")
searchCriteria.Promo = GetTriadicResponse("Promo (y/n/E):")
searchCriteria.Language = GetStringResponse("Language:")
shouldRefreshSearch = true
return searchCriteria
}
func getTriadicDisplay(triadic logic.Triadic) string {
if triadic == logic.True {
return "True"
}
if triadic == logic.False {
return "False"
}
return "Either"
}
func showInsertSearchCriteria(insertSearchCriteria logic.SearchCriteria) {
fmt.Println("SEARCH CRITERIA")
setCodeDisplay := getInfoDisplay(insertSearchCriteria.SetCode)
foilDisplay := getTriadicDisplay(insertSearchCriteria.Foil)
promoDisplay := getTriadicDisplay(insertSearchCriteria.Promo)
languageDisplay := getInfoDisplay(insertSearchCriteria.Language)
fmt.Println("Set code:", setCodeDisplay)
fmt.Println("Foil:", foilDisplay)
fmt.Println("Promo:", promoDisplay)
fmt.Println("Language:", languageDisplay)
fmt.Print("\n")
}
func showSelectedCard() {
selectedCardDisplay := getInfoDisplay(selectedCardPrintingSearchLine)
fmt.Println("Selected card:", selectedCardDisplay)
}
func getSearchOptions(db *sql.DB, insertSearchCriteria logic.SearchCriteria) logic.InsertSearchOptions {
options, err := logic.GetAllSearchOptions(db, insertSearchCriteria)
logic.Check(err)
shouldRefreshSearch = false
return options
}

View File

@ -1,19 +0,0 @@
package cli
import (
"sevenkeys/figlet"
"github.com/inancgumus/screen"
)
func ClearScreen() {
screen.Clear()
screen.MoveTopLeft()
}
func ShowSplashScreen() {
ClearScreen()
figlet.PrintMsgSlant("SEVENKEYS", "center")
figlet.PrintMsgTerm("the ultimate Magic: the Gathering trading card storage system", "center")
}

View File

@ -1,36 +0,0 @@
package cli
import (
"fmt"
"io"
"sevenkeys/database"
)
var (
selectedCardPrintingSearchLine string
copiesInserted int
)
func getInfoDisplay(info string) string {
if info == "" {
return "[EMPTY]"
}
return info
}
func showStorageInfo(w io.Writer, area database.StorageArea) {
fmt.Fprint(w, "Selected Storage Area: ")
if area.Name == "" { // TODO: Add a struct method to determine whether it is unset?
fmt.Fprint(w, "[None]\n")
return
}
fmt.Fprintf(w, "%s (%s)\n", area.Name, area.StorageType)
return
}
func showCopiesInserted() {
fmt.Println("Copies inserted:", copiesInserted)
}

View File

@ -1,39 +0,0 @@
package cli
import (
"bytes"
"sevenkeys/database"
"testing"
)
func Test_showStorageInfo_DisplaysNone_IfSelectedStorageAreaIsUnset(t *testing.T) {
expected := "Selected Storage Area: [None]\n"
var output bytes.Buffer
var area database.StorageArea
showStorageInfo(&output, area)
result := output.String()
if result != expected {
t.Errorf("expected %s, got %s", expected, result)
}
}
func Test_showStorageInfo_DisplaysStorageAreaNameAndType_IfSelectedStorageAreaIsSet(t *testing.T) {
expected := "Selected Storage Area: Test A (Box)\n"
var output bytes.Buffer
area := database.StorageArea{
Id: 1,
Name: "Test A",
Type: "Box",
}
showStorageInfo(&output, area)
result := output.String()
if result != expected {
t.Errorf("expected %s, got %s", expected, result)
}
}

View File

@ -1,37 +0,0 @@
package cli
import (
"database/sql"
"fmt"
"sevenkeys/logic"
"sevenkeys/logic/scryfall"
)
func RunUpdateCheck(db *sql.DB) {
fmt.Println("Checking for updates...")
bulkData, err := scryfall.GetBulkDataByType(scryfall.BulkDataTypeAllCards)
logic.Check(err)
needsUpdate, err := logic.CheckForUpdates(db, bulkData)
logic.Check(err)
if !needsUpdate {
fmt.Println("No update required.")
return
}
fmt.Println("Update required.")
if GetYesNoResponse("Run update? (y/N)") {
fmt.Println("Running update...")
logic.CreateCacheDirectories()
err = logic.UpdateSets(db)
logic.Check(err)
err = logic.UpdateCards(db, bulkData)
logic.Check(err)
fmt.Println("Update finished.")
}
}

View File

@ -26,22 +26,27 @@ func GetLastPositionInStorageArea(db *sql.DB, storageAreaId int) (int, error) {
return lastPosition, nil return lastPosition, nil
} }
func InsertCardLocation(db *sql.DB, storageLocation CardLocation) error { func InsertCardLocation(db *sql.DB, storageLocation CardLocation) (int64, error) {
query := `INSERT INTO CardLocation query := `INSERT INTO CardLocation
(CardPrintingId, StorageAreaId, Position) (CardPrintingId, StorageAreaId, Position)
VALUES (?, ?, ?);` VALUES (?, ?, ?);`
insert, err := db.Prepare(query) insert, err := db.Prepare(query)
if err != nil { if err != nil {
return err return -1, err
} }
_, err = insert.Exec(storageLocation.CardPrintingId, storageLocation.StorageAreaId, storageLocation.Position) result, err := insert.Exec(storageLocation.CardPrintingId, storageLocation.StorageAreaId, storageLocation.Position)
if err != nil { if err != nil {
return err return -1, err
} }
return nil id, err := result.LastInsertId()
if err != nil {
return -1, err
}
return id, nil
} }
func InsertCardInExistingLocation(db *sql.DB, cardLocation CardLocation) error { func InsertCardInExistingLocation(db *sql.DB, cardLocation CardLocation) error {

View File

@ -20,6 +20,33 @@ type LocateCardResult struct {
Position int Position int
} }
func GetLocateResultByCardLocationId(db *sql.DB, cardLocationId int) (LocateCardResult, error) {
var result LocateCardResult
query := `SELECT CardLocation.Id,
CardPrinting.Name,
CardPrinting.SetCode,
CardPrinting.IsFoil,
CardPrinting.IsPromo,
CardPrinting.CollectorNumber,
CardPrinting.Language,
StorageArea.Id,
StorageArea.StorageType,
StorageArea.Name,
CardLocation.Position
FROM CardLocation
JOIN CardPrinting ON CardLocation.CardPrintingId = CardPrinting.Id
JOIN StorageArea ON CardLocation.StorageAreaId = StorageArea.Id
WHERE CardLocation.Id = ?;`
err := db.QueryRow(query, cardLocationId).Scan(&result.CardLocationId, &result.CardName, &result.SetCode, &result.IsFoil, &result.IsPromo, &result.CollectorNumber, &result.Language, &result.StorageAreaId, &result.StorageAreaType, &result.StorageAreaName, &result.Position)
if err != nil {
return result, err
}
return result, nil
}
func GetLocateResults(db *sql.DB, cardNames []string) ([]LocateCardResult, error) { func GetLocateResults(db *sql.DB, cardNames []string) ([]LocateCardResult, error) {
var results []LocateCardResult var results []LocateCardResult
@ -61,3 +88,39 @@ func GetLocateResults(db *sql.DB, cardNames []string) ([]LocateCardResult, error
return results, nil return results, nil
} }
func GetAllStoredCards(db *sql.DB) ([]LocateCardResult, error) {
var results []LocateCardResult
query := `SELECT CardLocation.Id,
CardPrinting.Name,
CardPrinting.SetCode,
CardPrinting.IsFoil,
CardPrinting.IsPromo,
CardPrinting.CollectorNumber,
CardPrinting.Language,
StorageArea.Id,
StorageArea.StorageType,
StorageArea.Name,
CardLocation.Position
FROM CardLocation
JOIN CardPrinting ON CardLocation.CardPrintingId = CardPrinting.Id
JOIN StorageArea ON CardLocation.StorageAreaId = StorageArea.Id`
rows, err := db.Query(query)
defer rows.Close()
if err != nil {
return results, err
}
var result LocateCardResult
for rows.Next() {
err := rows.Scan(&result.CardLocationId, &result.CardName, &result.SetCode, &result.IsFoil, &result.IsPromo, &result.CollectorNumber, &result.Language, &result.StorageAreaId, &result.StorageAreaType, &result.StorageAreaName, &result.Position)
if err != nil {
return results, err
}
results = append(results, result)
}
return results, nil
}

View File

@ -26,6 +26,28 @@ CREATE TABLE IF NOT EXISTS CardPrinting (
Language VARCHAR(3) NOT NULL Language VARCHAR(3) NOT NULL
); );
/*
INSERT INTO CardPrinting (
Id,
Name,
SetCode,
IsFoil,
IsPromo,
CollectorNumber,
ImageUrl,
Language
) VALUES (
'00000000-0000-0000-0000-0000000000000',
'Scanned Card Placeholder',
'lea',
0,
0,
0,
'',
'en'
);
*/
CREATE TABLE IF NOT EXISTS StorageArea ( CREATE TABLE IF NOT EXISTS StorageArea (
Id INT AUTO_INCREMENT PRIMARY KEY, Id INT AUTO_INCREMENT PRIMARY KEY,
Name VARCHAR(100) NOT NULL, Name VARCHAR(100) NOT NULL,
@ -40,3 +62,11 @@ CREATE TABLE IF NOT EXISTS CardLocation (
FOREIGN KEY (StorageAreaId) REFERENCES StorageArea(Id), FOREIGN KEY (StorageAreaId) REFERENCES StorageArea(Id),
Position INT NULL Position INT NULL
); );
ALTER TABLE CardLocation ADD CardtraderProductId INT NULL;
CREATE TABLE IF NOT EXISTS CardScan (
Id INT PRIMARY KEY AUTO_INCREMENT,
CardLocationId INT NOT NULL,
Filename VARCHAR(100) NOT NULL
);

9
sevenkeys/delverlens_import Executable file
View File

@ -0,0 +1,9 @@
#!/bin/sh
set -e
filename="$(adb shell 'find /sdcard/Download/ | grep Temporary')"
adb pull "$filename" /home/viciouscirce/dox/sevenkeys_imports/import.csv
adb shell "rm $filename"
./sevenkeys --profile=production import import.csv
rm /home/viciouscirce/dox/sevenkeys_imports/import.csv

View File

@ -11,24 +11,33 @@ require (
require ( require (
filippo.io/edwards25519 v1.1.0 // indirect filippo.io/edwards25519 v1.1.0 // indirect
github.com/PuerkitoBio/goquery v1.8.1 // indirect
github.com/andybalholm/cascadia v1.3.2 // indirect
github.com/atotto/clipboard v0.1.4 // indirect github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/lipgloss v0.9.1 // indirect github.com/charmbracelet/lipgloss v0.9.1 // indirect
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
github.com/inancgumus/screen v0.0.0-20190314163918-06e984b86ed3 // indirect github.com/inancgumus/screen v0.0.0-20190314163918-06e984b86ed3 // indirect
github.com/jmoiron/sqlx v1.4.0 // indirect github.com/jmoiron/sqlx v1.4.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/lukesampson/figlet v0.0.0-20190211215653-8a3ef4a6ac42 // indirect github.com/lukesampson/figlet v0.0.0-20190211215653-8a3ef4a6ac42 // indirect
github.com/mattn/go-isatty v0.0.18 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mroth/weightedrand/v2 v2.1.0 // indirect
github.com/mtgban/go-mtgban v0.0.0-20241115160804-6b7158d200d7 // indirect
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/reflow v0.3.0 // indirect github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.15.2 // indirect github.com/muesli/termenv v0.15.2 // indirect
github.com/rivo/uniseg v0.4.6 // indirect github.com/rivo/uniseg v0.4.6 // indirect
golang.org/x/crypto v0.24.0 // indirect golang.org/x/crypto v0.24.0 // indirect
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sync v0.7.0 // indirect golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.21.0 // indirect golang.org/x/sys v0.21.0 // indirect
golang.org/x/term v0.21.0 // indirect golang.org/x/term v0.21.0 // indirect

View File

@ -1,5 +1,10 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
@ -18,6 +23,12 @@ github.com/go-mysql/errors v0.0.0-20180603193453-03314bea68e0 h1:meiLwrW6ukHHehy
github.com/go-mysql/errors v0.0.0-20180603193453-03314bea68e0/go.mod h1:ZH8V0509n2OSZLMYTMHzcy4hqUB+rG8ghK1zsP4i5gE= github.com/go-mysql/errors v0.0.0-20180603193453-03314bea68e0/go.mod h1:ZH8V0509n2OSZLMYTMHzcy4hqUB+rG8ghK1zsP4i5gE=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
github.com/inancgumus/screen v0.0.0-20190314163918-06e984b86ed3 h1:fO9A67/izFYFYky7l1pDP5Dr0BTCRkaQJUG6Jm5ehsk= github.com/inancgumus/screen v0.0.0-20190314163918-06e984b86ed3 h1:fO9A67/izFYFYky7l1pDP5Dr0BTCRkaQJUG6Jm5ehsk=
github.com/inancgumus/screen v0.0.0-20190314163918-06e984b86ed3/go.mod h1:Ey4uAp+LvIl+s5jRbOHLcZpUDnkjLBROl15fZLwPlTM= github.com/inancgumus/screen v0.0.0-20190314163918-06e984b86ed3/go.mod h1:Ey4uAp+LvIl+s5jRbOHLcZpUDnkjLBROl15fZLwPlTM=
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
@ -29,12 +40,17 @@ github.com/lukesampson/figlet v0.0.0-20190211215653-8a3ef4a6ac42 h1:UtyD+eBVdLYS
github.com/lukesampson/figlet v0.0.0-20190211215653-8a3ef4a6ac42/go.mod h1:/peI0OaxVYh7fzA72CD7rUsyGVdF7sCiFw7GcYqOcCw= github.com/lukesampson/figlet v0.0.0-20190211215653-8a3ef4a6ac42/go.mod h1:/peI0OaxVYh7fzA72CD7rUsyGVdF7sCiFw7GcYqOcCw=
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU=
github.com/mroth/weightedrand/v2 v2.1.0/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU=
github.com/mtgban/go-mtgban v0.0.0-20241115160804-6b7158d200d7 h1:AKVe40N5i3GhO2sOq8Uipqr6/g5Ihln8AUcdReN8IVI=
github.com/mtgban/go-mtgban v0.0.0-20241115160804-6b7158d200d7/go.mod h1:0YMKwBLKOogXJdaTXsfr9sqHr0O0XJgDyGujjcQHbo4=
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34= github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34=
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
@ -47,23 +63,64 @@ github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ
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/rivo/uniseg v0.4.6 h1:Sovz9sDSwbOz9tgUy8JpT+KgCkPYJEN/oYzlJiYTNLg= github.com/rivo/uniseg v0.4.6 h1:Sovz9sDSwbOz9tgUy8JpT+KgCkPYJEN/oYzlJiYTNLg=
github.com/rivo/uniseg v0.4.6/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.6/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@ -1,9 +1,16 @@
package logic package logic
import "sevenkeys/database" import (
"sevenkeys/database"
"strings"
)
func filterPrinting(printing database.CardPrinting, searchCriteria SearchCriteria) bool { func filterPrinting(printing database.CardPrinting, searchCriteria SearchCriteria) bool {
if searchCriteria.SetCode != "" && printing.SetCode != searchCriteria.SetCode { if searchCriteria.SetCode != "" && !strings.Contains(printing.SetCode, searchCriteria.SetCode) {
return true
}
if searchCriteria.CollectorNumber != "" && !strings.Contains(printing.CollectorNumber, searchCriteria.CollectorNumber) {
return true return true
} }

View File

@ -400,3 +400,27 @@ func TestFilterPrinting_ReturnsFalse_IfLanguageNotSet(t *testing.T) {
t.Errorf("filter was true") t.Errorf("filter was true")
} }
} }
func TestFilterPrinting_ReturnsTrue_IfSetCodeMatchesButCollectorNumberDoesnt(t *testing.T) {
printing := database.CardPrinting{
SetCode: "rtr",
CollectorNumber: "301",
IsFoil: false,
IsPromo: false,
Language: "en",
}
searchCriteria := SearchCriteria{
SetCode: "rtr",
CollectorNumber: "299",
Foil: False,
Promo: False,
Language: "",
}
filter := filterPrinting(printing, searchCriteria)
if filter != true {
t.Errorf("filter was false")
}
}

View File

@ -30,6 +30,17 @@ func GetBinderLocationDescription(position int) string {
return fmt.Sprintf(" on page %d in %s slot %d", page, frontOrBack, slot) return fmt.Sprintf(" on page %d in %s slot %d", page, frontOrBack, slot)
} }
func GetCardAtLocation(db *sql.DB, cardLocationId int) (database.LocateCardResult, error) {
var result database.LocateCardResult
result, err := database.GetLocateResultByCardLocationId(db, cardLocationId)
if err != nil {
return result, err
}
return result, nil
}
func LocateCards(db *sql.DB, cardNames []string, criteria SearchCriteria) ([]database.LocateCardResult, error) { func LocateCards(db *sql.DB, cardNames []string, criteria SearchCriteria) ([]database.LocateCardResult, error) {
results, err := database.GetLocateResults(db, cardNames) results, err := database.GetLocateResults(db, cardNames)
if err != nil { if err != nil {

View File

@ -20,17 +20,16 @@ const (
type SearchCriteria struct { type SearchCriteria struct {
SetCode string SetCode string
CollectorNumber string
Foil Triadic Foil Triadic
Promo Triadic Promo Triadic
Language string Language string
} }
// The InsertSearchOptions type is a map of `string` keys (which can be searched using type CardPrintingSearchOptions map[string]string
// fzf) to `string` values (which represent a primary key in the CardPrintings table)
type InsertSearchOptions map[string]string
func GetAllSearchOptions(db *sql.DB, searchCriteria SearchCriteria) (InsertSearchOptions, error) { func GetAllCardPrintingSearchOptions(db *sql.DB, searchCriteria SearchCriteria) (CardPrintingSearchOptions, error) {
var searchOptions InsertSearchOptions = make(map[string]string) var searchOptions CardPrintingSearchOptions = make(CardPrintingSearchOptions)
cardPrintings, err := database.GetAllCardPrintings(db) cardPrintings, err := database.GetAllCardPrintings(db)
if err != nil { if err != nil {
@ -61,6 +60,24 @@ func GetAllSearchOptions(db *sql.DB, searchCriteria SearchCriteria) (InsertSearc
return searchOptions, err return searchOptions, err
} }
type StorageSearchOptions map[string]int
func GetAllStorageSearchOptions(db *sql.DB) (StorageSearchOptions, error) {
var searchOptions StorageSearchOptions = make(StorageSearchOptions)
storedCards, err := database.GetAllStoredCards(db)
if err != nil {
return searchOptions, nil
}
for _, storedCard := range storedCards {
searchString := GetLocationDescription(storedCard)
searchOptions[searchString] = storedCard.CardLocationId
}
return searchOptions, nil
}
func GenericSearch[pk string | int](options map[string]pk) (pk, string, error) { func GenericSearch[pk string | int](options map[string]pk) (pk, string, error) {
var value pk var value pk

View File

@ -59,20 +59,20 @@ func GetStorageAreaSearchOptions(db *sql.DB) (StorageAreaSearchOptions, error) {
return options, nil return options, nil
} }
func storeAfterLastCard(db *sql.DB, cardLocation database.CardLocation) error { func storeAfterLastCard(db *sql.DB, cardLocation database.CardLocation) (int64, error) {
lastPosition, err := database.GetLastPositionInStorageArea(db, cardLocation.StorageAreaId) lastPosition, err := database.GetLastPositionInStorageArea(db, cardLocation.StorageAreaId)
if err != nil { if err != nil {
return err return -1, err
} }
cardLocation.Position = lastPosition + 1 cardLocation.Position = lastPosition + 1
err = database.InsertCardLocation(db, cardLocation) id, err := database.InsertCardLocation(db, cardLocation)
if err != nil { if err != nil {
return err return -1, err
} }
return nil return id, nil
} }
func storeInEmptySlot(db *sql.DB, cardLocation database.CardLocation, cardLocationId int) error { func storeInEmptySlot(db *sql.DB, cardLocation database.CardLocation, cardLocationId int) error {
@ -85,36 +85,47 @@ func storeInEmptySlot(db *sql.DB, cardLocation database.CardLocation, cardLocati
return nil return nil
} }
func StoreCard(db *sql.DB, cardLocation database.CardLocation) error { func StoreCard(db *sql.DB, cardLocation database.CardLocation) (int64, error) {
storageAreaType, err := database.GetStorageAreaTypeById(db, cardLocation.StorageAreaId) storageAreaType, err := database.GetStorageAreaTypeById(db, cardLocation.StorageAreaId)
if err != nil { if err != nil {
return err return -1, err
} }
if storageAreaType == database.StorageAreaTypeBinder { if storageAreaType == database.StorageAreaTypeBinder {
nextEmptySlotId, err := database.GetNextEmptySlotInBinder(db, cardLocation.StorageAreaId) nextEmptySlotId, err := database.GetNextEmptySlotInBinder(db, cardLocation.StorageAreaId)
if err == database.ErrNoEmptySlotsInBinder { if err == database.ErrNoEmptySlotsInBinder {
err = storeAfterLastCard(db, cardLocation) id, err := storeAfterLastCard(db, cardLocation)
if err != nil { if err != nil {
return err return -1, err
} }
return id, nil
} else if err != nil { } else if err != nil {
return err return -1, err
} else { } else {
err = storeInEmptySlot(db, cardLocation, nextEmptySlotId) err = storeInEmptySlot(db, cardLocation, nextEmptySlotId)
if err != nil { if err != nil {
return err return -1, err
} }
} }
return nil return int64(nextEmptySlotId), nil
} }
err = storeAfterLastCard(db, cardLocation) id, err := storeAfterLastCard(db, cardLocation)
if err != nil { if err != nil {
return err return -1, err
} }
return nil return id, nil
}
func Replace(db *sql.DB, cardLocationId int, cardPrintingId string) error {
cardLocation := database.CardLocation{
Id: cardLocationId,
CardPrintingId: cardPrintingId,
}
return database.InsertCardInExistingLocation(db, cardLocation)
} }

View File

@ -1,18 +1,52 @@
package main package main
import ( import (
"bufio"
"encoding/json"
"errors"
"flag" "flag"
"fmt" "fmt"
"io/ioutil"
"log"
"os" "os"
"sevenkeys/cli"
"sevenkeys/database" "sevenkeys/database"
"sevenkeys/delverlens" "sevenkeys/delverlens"
"sevenkeys/figlet"
"sevenkeys/logic" "sevenkeys/logic"
"sevenkeys/logic/scryfall"
"strings"
"github.com/mtgban/go-mtgban/cardtrader"
) )
func GetStringResponse(prompt string) string {
fmt.Print(prompt + " ")
var response string
scanner := bufio.NewScanner(os.Stdin)
scanner.Scan()
response = scanner.Text()
return response
}
func GetYesNoResponse(prompt string) bool {
response := GetStringResponse(prompt)
return strings.ToUpper(response) == "Y"
}
const ( const (
UpdateSubcommand string = "update"
CreateStorageAreaSubcommand string = "createstorage"
StoreSubcommand string = "store"
ImportSubcommand string = "import" ImportSubcommand string = "import"
SearchPrintingsSubcommand string = "search-printings"
SearchStorageSubcommand string = "search-storage"
AddSubcommand string = "add"
RemoveSubcommand string = "remove"
ReplaceSubcommand string = "replace"
DeckSubcommand string = "deck"
GetProductIdSubcommand string = "products"
) )
func main() { func main() {
@ -22,15 +56,16 @@ func main() {
flag.Parse() flag.Parse()
db := database.GetDatabaseFromConfig("config." + profile + ".json") db := database.GetDatabaseFromConfig("config." + profile + ".json")
/* Sad.
figlet.ReadFigletFonts() figlet.ReadFigletFonts()
cli.ShowSplashScreen() cli.ShowSplashScreen()
cli.RunUpdateCheck(db) */
// TODO: Decide in what form we need to retain this functionality if any // TODO: Decide in what form we need to retain this functionality if any
//cli.MainCliLoop(db) //cli.MainCliLoop(db)
importCmd := flag.NewFlagSet("import", flag.ExitOnError) //searchCmd := flag.NewFlagSet(SearchSubcommand, flag.ExitOnError)
storageArea := importCmd.String("storagearea", "", "The name of the StorageArea where cards should be imported.") //name := searchCmd.String("name", "", "The card name to search for.")
if len(flag.Args()) == 0 { if len(flag.Args()) == 0 {
fmt.Fprintln(os.Stderr, "Please specify a subcommand.") fmt.Fprintln(os.Stderr, "Please specify a subcommand.")
@ -38,7 +73,77 @@ func main() {
} }
switch flag.Args()[0] { switch flag.Args()[0] {
case UpdateSubcommand:
fmt.Println("Checking for updates...")
bulkData, err := scryfall.GetBulkDataByType(scryfall.BulkDataTypeAllCards)
logic.Check(err)
needsUpdate, err := logic.CheckForUpdates(db, bulkData)
logic.Check(err)
if !needsUpdate {
fmt.Println("No update required.")
return
}
fmt.Println("Update required.")
if GetYesNoResponse("Run update? (y/N)") {
fmt.Println("Running update...")
logic.CreateCacheDirectories()
err = logic.UpdateSets(db)
logic.Check(err)
err = logic.UpdateCards(db, bulkData)
logic.Check(err)
fmt.Println("Update finished.")
}
break
case CreateStorageAreaSubcommand:
createStorageCmd := flag.NewFlagSet(CreateStorageAreaSubcommand, flag.ExitOnError)
storageAreaName := createStorageCmd.String("name", "",
"The name of the StorageArea to create.")
storageAreaType := createStorageCmd.String("type", "",
"The name of the StorageArea to create.")
createStorageCmd.Parse(flag.Args()[1:])
storageArea := database.StorageArea{Name: *storageAreaName, StorageType: *storageAreaType}
err := logic.CreateStorageArea(db, storageArea)
logic.Check(err)
break
case StoreSubcommand:
storeCmd := flag.NewFlagSet(StoreSubcommand, flag.ExitOnError)
storageArea := storeCmd.String("storagearea", "",
"The name of the StorageArea the card should be inserted to.")
id := storeCmd.String("id", "", "The CardPrintingId of the card to store.")
storeCmd.Parse(flag.Args()[1:])
storageAreaId, err := logic.GetStorageAreaId(db, *storageArea)
if err == logic.ErrCouldNotGetStorageAreaId {
fmt.Fprintf(os.Stderr, "[sevenkeys] No storage area was selected, exiting.\n")
os.Exit(1)
}
cardLocation := database.CardLocation{
CardPrintingId: *id,
StorageAreaId: storageAreaId,
}
cardLocationId, err := logic.StoreCard(db, cardLocation)
logic.Check(err)
fmt.Printf("%d\n", cardLocationId)
break
case ImportSubcommand: case ImportSubcommand:
importCmd := flag.NewFlagSet(ImportSubcommand, flag.ExitOnError)
storageArea := importCmd.String("storagearea", "",
"The name of the StorageArea where cards should be imported.")
importCmd.Parse(flag.Args()[1:]) importCmd.Parse(flag.Args()[1:])
storageAreaId, err := logic.GetStorageAreaId(db, *storageArea) storageAreaId, err := logic.GetStorageAreaId(db, *storageArea)
@ -54,6 +159,146 @@ func main() {
err = logic.ImportDelverLensCards(db, delverLensCards, storageAreaId) err = logic.ImportDelverLensCards(db, delverLensCards, storageAreaId)
logic.Check(err) logic.Check(err)
break break
case SearchPrintingsSubcommand:
searchPrintingsCmd := flag.NewFlagSet(SearchPrintingsSubcommand, flag.ExitOnError)
setCode := searchPrintingsCmd.String("set-code", "", "The code for the set the card we're searching for belongs to.")
collectorNumber := searchPrintingsCmd.String("collector-number", "", "The collector number of the card we're searching for.")
foil := searchPrintingsCmd.String("foil", "E", "Whether the card we're searching for is foil.")
searchPrintingsCmd.Parse(flag.Args()[1:])
searchCriteria := logic.SearchCriteria{
SetCode: *setCode,
CollectorNumber: *collectorNumber,
Promo: logic.Either,
Language: "en",
}
if *foil == "Y" {
searchCriteria.Foil = logic.True
} else if *foil == "N" {
searchCriteria.Foil = logic.False
} else {
searchCriteria.Foil = logic.Either
}
searchOptions, err := logic.GetAllCardPrintingSearchOptions(db, searchCriteria)
logic.Check(err)
id, _, err := logic.GenericSearch(searchOptions)
logic.Check(err)
fmt.Println(id)
break
case SearchStorageSubcommand:
searchOptions, err := logic.GetAllStorageSearchOptions(db)
logic.Check(err)
id, _, err := logic.GenericSearch(searchOptions)
logic.Check(err)
fmt.Println(id)
break
case AddSubcommand:
addCmd := flag.NewFlagSet(AddSubcommand, flag.ExitOnError)
cardPrintingId := addCmd.String("card-printing-id", "", "The ID of the card printing to add to storage.")
storageArea := addCmd.String("storagearea", "",
"The name of the StorageArea where cards should be imported.")
addCmd.Parse(flag.Args()[1:])
storageAreaId, err := logic.GetStorageAreaId(db, *storageArea)
if err == logic.ErrCouldNotGetStorageAreaId {
fmt.Fprintf(os.Stderr, "[sevenkeys] No storage area was selected, exiting.\n")
os.Exit(1)
}
logic.Check(err)
cardLocation := database.CardLocation{
CardPrintingId: *cardPrintingId,
StorageAreaId: storageAreaId,
}
logic.StoreCard(db, cardLocation)
break
case RemoveSubcommand:
removeCmd := flag.NewFlagSet(RemoveSubcommand, flag.ExitOnError)
cardLocationId := removeCmd.Int("card-location-id", -1, "The card location to remove the card at.")
removeCmd.Parse(flag.Args()[1:])
if *cardLocationId == -1 {
log.Fatal(errors.New("No CardLocationId given."))
}
location, err := logic.GetCardAtLocation(db, *cardLocationId)
logic.Check(err)
err = logic.RemoveFromStorage(db, location)
logic.Check(err)
break
case ReplaceSubcommand:
replaceCmd := flag.NewFlagSet(ReplaceSubcommand, flag.ExitOnError)
cardLocationId := replaceCmd.Int("card-location-id", -1, "The card location to replace the card at.")
cardPrintingId := replaceCmd.String("card-printing-id", "", "The card printing to put at the specified location.")
replaceCmd.Parse(flag.Args()[1:])
if *cardLocationId == -1 {
log.Fatal(errors.New("No CardLocationId given."))
}
if *cardPrintingId == "" {
log.Fatal(errors.New("No CardPrintingId given."))
}
err := logic.Replace(db, *cardLocationId, *cardPrintingId)
logic.Check(err)
break
case DeckSubcommand:
deckCmd := flag.NewFlagSet(DeckSubcommand, flag.ExitOnError)
deckCmd.Parse(flag.Args()[1:])
//filename := deckCmd.Args()[0]
break
case GetProductIdSubcommand:
blbBlueprintsBytes, err := ioutil.ReadFile("blb_blueprints.json")
logic.Check(err)
var blbBlueprints []cardtrader.Blueprint
err = json.Unmarshal(blbBlueprintsBytes, &blbBlueprints)
logic.Check(err)
productsBytes, err := ioutil.ReadFile("products.json")
logic.Check(err)
var products []cardtrader.Product
err = json.Unmarshal(productsBytes, &products)
logic.Check(err)
for _, product := range products {
var productBlueprint cardtrader.Blueprint
for _, blueprint := range blbBlueprints {
if blueprint.Id == product.BlueprintId {
productBlueprint = blueprint
break
}
}
fmt.Printf("%s %s %d ",
productBlueprint.Name,
product.Properties.Number,
product.Id,
)
if product.Properties.MTGFoil {
fmt.Printf("FOIL ")
} else {
fmt.Printf("NONFOIL ")
}
fmt.Printf("x%d\n", product.Quantity)
}
break
default: default:
fmt.Fprintf(os.Stderr, "Unrecognized subcommand: %s\n", os.Args[1]) fmt.Fprintf(os.Stderr, "Unrecognized subcommand: %s\n", os.Args[1])
break break

3
sevenkeys/migration Executable file
View File

@ -0,0 +1,3 @@
#!/bin/sh
goose -s -dir database/migrations/ create "$1" sql

3
sevenkeys/remove_from_storage Executable file
View File

@ -0,0 +1,3 @@
#!/bin/sh
sevenkeys --profile=production remove --card-location-id="$(sevenkeys --profile=production search-storage)"