Compare commits
20 Commits
bcf68c8332
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 5d21571561 | |||
| 821d301de3 | |||
| 6b5034c544 | |||
| 43a75c0b92 | |||
| 130287caa7 | |||
| 3df07d7622 | |||
| fd358f5d3f | |||
| f1beae9198 | |||
| 4dde40b72e | |||
| a21520b5e5 | |||
| af1ac9b5fa | |||
| 9f9a0b1fb7 | |||
| 3e1b89312a | |||
| 3c78637809 | |||
| e40a64579b | |||
| e966a22707 | |||
| 3f7cd6353a | |||
| 6ff0204189 | |||
| 9a8c971d73 | |||
| b6664492fa |
1
misc_scripts/mount_truenas_music
Normal file
1
misc_scripts/mount_truenas_music
Normal file
@@ -0,0 +1 @@
|
||||
sudo mount -t cifs -o uid=arthurr,username=arthurr //truenas.local/Music /home/arthurr/TruenasMusic/
|
||||
@@ -9,6 +9,7 @@ edition = "2021"
|
||||
chrono = { version = "0.4.39", features = ["serde"] }
|
||||
clap = { version = "4.5.42", features = ["derive"] }
|
||||
closestmatch = "0.1.2"
|
||||
deunicode = "1.6.2"
|
||||
dir_spec = "0.2.0"
|
||||
rusqlite = "0.37.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
||||
113
scryfall_deser/README.md
Normal file
113
scryfall_deser/README.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# Quick Magic Finder
|
||||
|
||||
A quick way to search up Magic the Gathering (TM) cards for Linux (maybe MacOS? Don't have one, so haven't tried).
|
||||
|
||||
Currently, no images are displayed - so don't go into this expecting that.
|
||||
|
||||
## The Components
|
||||
|
||||
This repo has 2 main parts to it:
|
||||
|
||||
* The `magic_finder` rust code, which does the "heavy lifting" of updating a database, searching through it for cards, and finding close names (kind of)
|
||||
* The supporting scripts which use [`rofi`](https://github.com/davatorium/rofi)
|
||||
|
||||
`magic_finder` can be used without the rofi parts if you wanted a command for loading showing basic mtg card info from the CLI.
|
||||
|
||||
The `rofi` part is so that I can quickly and easily get the card info I want. Basically just adds a very simple and easy GUI to the `magic_finder` part. I've written 2 wrapper scripts to enable this.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Magic Finder
|
||||
`magic_finder` is written in rust. Check the Cargo.toml file for more specific requirements. I was compiling with rustc version 1.88, but I would not at all be surprised if it worked on rust from year(s) ago.
|
||||
|
||||
### `rofi`
|
||||
The rofi scripts require a version over (I think) 1.7.6. The scripts require the ability to set the `command` setting on the `filebrowser` option in rofi. From what I can tell, this was introduced in 1.7.6.
|
||||
|
||||
Very annoyingly, the Ubuntu repos do *not* have this version. They have an earlier version. So, to use this, you'll need to get a more recent version yourself. I compiled and installed myself. See the end of this README for how I did that.
|
||||
|
||||
## Usage
|
||||
|
||||
**TODO**
|
||||
|
||||
## Installation, First Usage, and Updating
|
||||
|
||||
|
||||
### Installation
|
||||
I am sorry in advance, this is a bit of a pain becuase of my lack of knowledge on how to properly "package" scripts alongside a rust binary.
|
||||
|
||||
**TODO - don't forget that I need to install sqlite3-lib/dev/something - or maybe I add the feature flag "bundled"**
|
||||
|
||||
*TODO - don't forget about adding the shortcut to the overall wrapper script*
|
||||
|
||||
## Before you use this
|
||||
|
||||
## Update
|
||||
Basically the same as the "Before you use this" secion. Go to the [Scryfall Bulk Download](https://scryfall.com/docs/api/bulk-data) page and get the Oracle Cards download.
|
||||
|
||||
Then run the `update_with_rofi.sh` script, locate the downloaded file, and it should Just Work (TM). If not, try updating this repo. If it still doesn't work, log a ticket. It's probably going to something with Scryfall updating their schema that I haven't accounted for. Alternatively, run `COMMAND --update <path to file>` where `<path to file>` is the full path to where you downloaded the file.
|
||||
|
||||
NOTE: Updating *will* delete the previous db - that shouldn't be a problem though, because you shouldn't use that unless you really know what you're doing.
|
||||
|
||||
## Why this exists
|
||||
|
||||
I like watch Magic the Gathering (TM) videos, expecially while coding, working, writing, whatever. Often, I don't know what card they're talking about. They'll often say the card name (sometimes a nickname - this tool doesn't help with that), and show it on the screen briefly (or in a tiny/obscured view), and I'll miss what it actually does. When this happens, I need to open a tab on my browser, go to [Scryfall](scryfall.com), type in the name, (sometimes) click the specific card, and the view it. This takes 2-3 page loads, changing my active window and just a bit of a pain.
|
||||
|
||||
This tool, especially using `rofi` enables me to hit `Ctrl+M`, type in the card name, navigate to the card (if needed) with my keyboard, and display the card. No browser, no HTTP, lower context switch, displayed right there, and goes away when I press anything else.
|
||||
|
||||
The idea is it's just easier and quicker than my normal process.
|
||||
|
||||
## How I Installed `rofi`
|
||||
If you're smarter than me, just follow the official docs: https://github.com/davatorium/rofi/blob/next/INSTALL.md and don't bother reading this.
|
||||
|
||||
I am entirely unfamiliar with `meson` and `ninja` (I'm more a `Makefile` kinda guy - haven't done any `C` properly in >10 years), so here's what I did. This is for Ubutntu - you will need to do something different for download the dependencies (you can see them in the INSTALL.md file referenced above).
|
||||
|
||||
Of note below, I'm installing this into my `$HOME/bin` directory. Change that part if you want to install somewhere else. Make sure to install it somewhere in your `$PATH` though!
|
||||
|
||||
Clone the repo and move into it
|
||||
|
||||
```
|
||||
git clone --recursive https://github.com/davatorium/rofi
|
||||
cd rofi
|
||||
```
|
||||
|
||||
Install the deps
|
||||
|
||||
```
|
||||
sudo apt build-dep rofi
|
||||
sudo apt install meson
|
||||
sudo apt install libxcb-keysyms1-dev libxcb-keysyms1 # I suspect only one of these is needed - not sure which
|
||||
```
|
||||
|
||||
Setup and build (not sure why I put the prefix in here... you'll see below I still copy+pasted the bin)
|
||||
```
|
||||
meson setup build --prefix $HOME/bin -Dwayland=disabled -Dxcb=enabled
|
||||
ninja -C build -v
|
||||
```
|
||||
|
||||
Fingers crossed that all compiled and stuff... then copy the bin
|
||||
|
||||
```
|
||||
cp build/rofi ~/bin
|
||||
```
|
||||
|
||||
## FIXME, TODO & Features That Could be Good
|
||||
* Remove the non-card cards. Examples I've come across are:
|
||||
** Planes: Black Lotus Lounge
|
||||
** Art Cards: https://scryfall.com/card/altr/15/%C3%A9owyn-fearless-knight-%C3%A9owyn-fearless-knight?utm_source=api
|
||||
* Allow exiting the script early (i.e. I hit CTRL+g just exits everything)
|
||||
* Misspelled cards, if only 1 hit that makes sense, could just work
|
||||
* Display the actual card image (probably won't do this)
|
||||
* Some kind of auto-magic direct link between the return codes set out in `main.r`s and the `rofi` scripts. Currently I need to manually make sure they're the same between the `rust` code and the `sh` code.
|
||||
I'm guessing would involve cargo build scripts (or just a find+replace?)
|
||||
* Add some classic nicknames (might be difficult to find them all). examples include:
|
||||
** Bob - Dark Confidant
|
||||
** AK - Accumulated Knowledge
|
||||
** find more here: https://mtg.wiki/page/List_of_Magic_slang/Card_nicknames
|
||||
|
||||
## Thanks
|
||||
|
||||
This project really is just 95% based on Scryfall. They're amazing. I don't know how or why they exist, but I think they're basically the best Magic the Gathering (TM) resource online.
|
||||
|
||||
Of course `rofi` for providing the quick, simple, low-weight, and well documented tool. Particularly the quickness and low-weight which made it really possible. The alternative was opening terminal windows, or using TKinter or something... surely not worth it.
|
||||
|
||||
Thanks to all the amazing `rust` packages I use.
|
||||
@@ -1,9 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/bin/sh
|
||||
|
||||
CARDS=$(/home/arthurr/code/mini_projects/scryfall_deser/target/debug/scryfall_deser $@)
|
||||
|
||||
SELECTION=$(rofi -dmenu <<< "$CARDS")
|
||||
|
||||
CARD_OUTPUT=$(/home/arthurr/code/mini_projects/scryfall_deser/target/debug/scryfall_deser --exact $SELECTION)
|
||||
|
||||
rofi -e "$CARD_OUTPUT"
|
||||
SEARCH_STRING=$(rofi -l 0 -dmenu)
|
||||
/home/arthurr/code/mini_projects/scryfall_deser/scripts/search_with_rofi_with_args.sh $SEARCH_STRING
|
||||
|
||||
74
scryfall_deser/scripts/search_with_rofi_with_args.sh
Executable file
74
scryfall_deser/scripts/search_with_rofi_with_args.sh
Executable file
@@ -0,0 +1,74 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Note to self in this... The whitespace seemed to fuck the ifs up. Not sure why.
|
||||
# This is why it's all flat and ugly
|
||||
|
||||
CARDS=$(/home/arthurr/code/mini_projects/scryfall_deser/target/debug/scryfall_deser $@)
|
||||
RETURN=$?
|
||||
echo $RETURN
|
||||
|
||||
#######################
|
||||
## Exact card found - just print the card
|
||||
#######################
|
||||
if [ $RETURN -eq 200 ]; then
|
||||
|
||||
rofi -e "$CARDS"
|
||||
|
||||
fi
|
||||
|
||||
|
||||
#######################
|
||||
## Cards to select from
|
||||
#######################
|
||||
if [ $RETURN -eq 0 ]; then
|
||||
|
||||
SELECTION=$(rofi -dmenu -i << EOF
|
||||
$CARDS
|
||||
EOF
|
||||
)
|
||||
|
||||
CARD_OUTPUT=$(/home/arthurr/code/mini_projects/scryfall_deser/target/debug/scryfall_deser --exact $SELECTION)
|
||||
|
||||
# If you double check the first rofi selection it seems to prevent the error window from popping up
|
||||
# I think this is because it registers the second click as a click outside the window which exits
|
||||
# the rofi -e message
|
||||
sleep 0.05
|
||||
|
||||
rofi -e "$CARD_OUTPUT"
|
||||
fi
|
||||
|
||||
##########################
|
||||
## Not even one card that matched - try a close string
|
||||
##########################
|
||||
if [ $RETURN -eq 105 ]; then
|
||||
|
||||
# TODO do something different with no matching string at all - perhaps even a different ExitCode?
|
||||
|
||||
SELECTION=$(rofi -dmenu -p "Did you mean?" -i << EOF
|
||||
$CARDS
|
||||
EOF
|
||||
)
|
||||
|
||||
CARDS=$(/home/arthurr/code/mini_projects/scryfall_deser/target/debug/scryfall_deser $SELECTION)
|
||||
|
||||
SELECTION=$(rofi -dmenu -i << EOF
|
||||
$CARDS
|
||||
EOF
|
||||
)
|
||||
|
||||
CARD_OUTPUT=$(/home/arthurr/code/mini_projects/scryfall_deser/target/debug/scryfall_deser --exact $SELECTION)
|
||||
|
||||
sleep 0.05
|
||||
|
||||
rofi -e "$CARD_OUTPUT"
|
||||
|
||||
fi
|
||||
|
||||
###############################
|
||||
## No seach string input at all
|
||||
###############################
|
||||
if [ $RETURN -eq 101 ]; then
|
||||
|
||||
rofi -e "No search string found"
|
||||
|
||||
fi
|
||||
9
scryfall_deser/scripts/update_with_rofi.sh
Executable file
9
scryfall_deser/scripts/update_with_rofi.sh
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/bin/sh
|
||||
|
||||
SCRYFALL_BULK=$(rofi -modi filebrowser -show filebrowser -filebrowser-command printf)
|
||||
echo "$SCRYFALL_BULK"
|
||||
|
||||
/home/arthurr/code/mini_projects/scryfall_deser/target/debug/scryfall_deser --update "$SCRYFALL_BULK"
|
||||
|
||||
# TODO - check return value
|
||||
rofi -e "Should be updated"
|
||||
@@ -1,8 +1,9 @@
|
||||
use deunicode::deunicode;
|
||||
use rusqlite;
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::str::SplitWhitespace;
|
||||
|
||||
use super::deser::ScryfallCard;
|
||||
use super::utils::get_local_cache_folder;
|
||||
@@ -38,65 +39,67 @@ pub fn get_all_lowercase_card_names() -> Vec<String> {
|
||||
card_names
|
||||
}
|
||||
|
||||
pub fn update_db_with_file(file: PathBuf) -> bool {
|
||||
let ac = fs::read_to_string(file).unwrap();
|
||||
let ac: Vec<ScryfallCard> = serde_json::from_str(&ac).unwrap();
|
||||
pub fn get_all_mtg_words() -> Vec<String> {
|
||||
let sqlite_file = get_local_data_sqlite_file();
|
||||
let mut conn = rusqlite::Connection::open(sqlite_file).unwrap();
|
||||
let tx = conn.transaction().unwrap();
|
||||
for card in ac {
|
||||
for word in card.name.split_whitespace() {
|
||||
let res = tx.execute(
|
||||
"INSERT INTO magic_words (word) VALUES (?1)
|
||||
ON CONFLICT (word) DO NOTHING;",
|
||||
[word.replace(",", "")],
|
||||
);
|
||||
let conn = rusqlite::Connection::open(sqlite_file).unwrap();
|
||||
let mut stmt = conn.prepare("SELECT word FROM mtg_words;").unwrap();
|
||||
let mut rows = stmt.query([]).unwrap();
|
||||
let mut card_names = Vec::new();
|
||||
while let Some(row) = rows.next().unwrap() {
|
||||
card_names.push(row.get(0).unwrap());
|
||||
}
|
||||
let lowercase_name = card.name.to_lowercase();
|
||||
let power_toughness = match card.power {
|
||||
Some(p) => format!("{}/{}", p, card.toughness.unwrap()),
|
||||
None => "".to_string(),
|
||||
};
|
||||
let oracle_text = match card.oracle_text {
|
||||
Some(ot) => ot,
|
||||
None => "".to_string(),
|
||||
};
|
||||
let loyalty = match card.loyalty {
|
||||
Some(loy) => loy,
|
||||
None => "".to_string(),
|
||||
};
|
||||
let mana_cost = match card.mana_cost {
|
||||
Some(mc) => mc,
|
||||
None => "".to_string(),
|
||||
};
|
||||
let res = tx.execute(
|
||||
"INSERT INTO cards (name, lowercase_name, type_line, oracle_text, power_toughness, loyalty, mana_cost, scryfall_uri) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
|
||||
[card.name, lowercase_name, card.type_line, oracle_text, power_toughness, loyalty, mana_cost, card.scryfall_uri],
|
||||
);
|
||||
}
|
||||
tx.commit();
|
||||
true
|
||||
card_names
|
||||
}
|
||||
|
||||
// unsure if this should be in this file...
|
||||
|
||||
impl fmt::Display for DbCard {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match &self.mana_cost {
|
||||
Some(mc) => writeln!(f, "{}\t{}", self.name, mc)?,
|
||||
None => writeln!(f, "{}", self.name)?,
|
||||
}
|
||||
writeln!(f, "{}", self.type_line)?;
|
||||
match &self.oracle_text {
|
||||
Some(ot) => writeln!(f, "{}", ot)?,
|
||||
None => (),
|
||||
}
|
||||
match &self.power_toughness {
|
||||
Some(pt) => writeln!(f, "{}", pt)?,
|
||||
None => (),
|
||||
}
|
||||
match &self.loyalty {
|
||||
Some(l) => writeln!(f, "Starting Loyalty: {}", l)?,
|
||||
None => (),
|
||||
}
|
||||
writeln!(f, "Scryfall URI: {}", self.scryfall_uri)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for DbCard {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.name.cmp(&other.name)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for DbCard {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for DbCard {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.name == other.name
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for DbCard {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DbCard {
|
||||
pub name: String,
|
||||
pub lowercase_name: String,
|
||||
pub type_line: String,
|
||||
pub oracle_text: Option<String>,
|
||||
pub power_toughness: Option<String>,
|
||||
@@ -115,30 +118,101 @@ pub fn get_card_by_name(name: &str, name_type: GetNameType) -> Option<DbCard> {
|
||||
let conn = rusqlite::Connection::open(sqlite_file).unwrap();
|
||||
let sql = match name_type {
|
||||
GetNameType::Name => {
|
||||
"SELECT name, type_line, oracle_text, power_toughness, loyalty, mana_cost, scryfall_uri
|
||||
"SELECT name, lowercase_name, type_line, oracle_text, power_toughness, loyalty, mana_cost, scryfall_uri
|
||||
FROM cards WHERE name = (?1)"
|
||||
}
|
||||
GetNameType::LowercaseName => {
|
||||
"SELECT name, type_line, oracle_text, power_toughness, loyalty, mana_cost, scryfall_uri
|
||||
"SELECT name, lowercase_name, type_line, oracle_text, power_toughness, loyalty, mana_cost, scryfall_uri
|
||||
FROM cards WHERE lowercase_name = (?1)"
|
||||
}
|
||||
};
|
||||
dbg!(name);
|
||||
dbg!(&sql);
|
||||
let mut stmt = conn.prepare(sql).unwrap();
|
||||
let mut rows = stmt.query([name]).unwrap();
|
||||
match rows.next().unwrap() {
|
||||
Some(row) => Some(DbCard {
|
||||
name: row.get(0).unwrap(),
|
||||
type_line: row.get(1).unwrap(),
|
||||
oracle_text: row.get(2).unwrap(),
|
||||
power_toughness: row.get(3).unwrap(),
|
||||
loyalty: row.get(4).unwrap(),
|
||||
mana_cost: row.get(5).unwrap(),
|
||||
scryfall_uri: row.get(6).unwrap(),
|
||||
lowercase_name: row.get(1).unwrap(),
|
||||
type_line: row.get(2).unwrap(),
|
||||
oracle_text: row.get(3).unwrap(),
|
||||
power_toughness: row.get(4).unwrap(),
|
||||
loyalty: row.get(5).unwrap(),
|
||||
mana_cost: row.get(6).unwrap(),
|
||||
scryfall_uri: row.get(7).unwrap(),
|
||||
}),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_matching_cards_scryfall_style(search_strings: &Vec<String>) -> Vec<DbCard> {
|
||||
assert!(!search_strings.is_empty());
|
||||
let sqlite_file = get_local_data_sqlite_file();
|
||||
let conn = rusqlite::Connection::open(sqlite_file).unwrap();
|
||||
let mut percentaged_string = Vec::new();
|
||||
// I know that .clone fixes my problem - I'm not sure why I need to though
|
||||
for mut search_string in search_strings.clone() {
|
||||
search_string.push('%');
|
||||
search_string.insert(0, '%');
|
||||
percentaged_string.push(search_string);
|
||||
}
|
||||
let mut sql: String = "SELECT name, lowercase_name, type_line, oracle_text, power_toughness, loyalty, mana_cost, scryfall_uri
|
||||
FROM cards WHERE".into();
|
||||
for i in 0..search_strings.len() {
|
||||
sql.push_str(&format!(" lowercase_name LIKE (?{}) AND", i + 1));
|
||||
}
|
||||
sql.pop();
|
||||
sql.pop();
|
||||
sql.pop();
|
||||
sql.pop();
|
||||
let mut stmt = conn.prepare(&sql).unwrap();
|
||||
stmt.query_map(rusqlite::params_from_iter(percentaged_string), |row| {
|
||||
Ok(DbCard {
|
||||
name: row.get(0).unwrap(),
|
||||
lowercase_name: row.get(1).unwrap(),
|
||||
type_line: row.get(2).unwrap(),
|
||||
oracle_text: row.get(3).unwrap(),
|
||||
power_toughness: row.get(4).unwrap(),
|
||||
loyalty: row.get(5).unwrap(),
|
||||
mana_cost: row.get(6).unwrap(),
|
||||
scryfall_uri: row.get(7).unwrap(),
|
||||
})
|
||||
})
|
||||
.unwrap()
|
||||
.filter_map(|res| res.ok())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn find_matching_cards(name: &str) -> Vec<DbCard> {
|
||||
let sqlite_file = get_local_data_sqlite_file();
|
||||
let conn = rusqlite::Connection::open(sqlite_file).unwrap();
|
||||
// There must be something better than this - although I don't think it's possible with a str
|
||||
let mut name = name.to_string();
|
||||
name.push('%');
|
||||
name.insert(0, '%');
|
||||
let mut stmt = conn
|
||||
.prepare(
|
||||
"SELECT name, lowercase_name, type_line, oracle_text, power_toughness, loyalty, mana_cost, scryfall_uri
|
||||
FROM cards WHERE lowercase_name LIKE (?1)",
|
||||
)
|
||||
.unwrap();
|
||||
stmt.query_map([name], |row| {
|
||||
Ok(DbCard {
|
||||
name: row.get(0).unwrap(),
|
||||
lowercase_name: row.get(1).unwrap(),
|
||||
type_line: row.get(2).unwrap(),
|
||||
oracle_text: row.get(3).unwrap(),
|
||||
power_toughness: row.get(4).unwrap(),
|
||||
loyalty: row.get(5).unwrap(),
|
||||
mana_cost: row.get(6).unwrap(),
|
||||
scryfall_uri: row.get(7).unwrap(),
|
||||
})
|
||||
})
|
||||
.unwrap()
|
||||
.filter_map(|res| res.ok())
|
||||
.collect()
|
||||
}
|
||||
|
||||
const CREATE_CARDS_TABLE_SQL: &str = "
|
||||
CREATE TABLE cards (
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
@@ -152,7 +226,7 @@ CREATE TABLE cards (
|
||||
)";
|
||||
|
||||
const CREATE_MAGIC_WORDS_TABLE_SQL: &str = "
|
||||
CREATE TABLE magic_words (
|
||||
CREATE TABLE mtg_words (
|
||||
word TEXT NOT NULL UNIQUE
|
||||
)";
|
||||
|
||||
@@ -172,3 +246,44 @@ pub fn init_db() -> bool {
|
||||
.unwrap();
|
||||
true
|
||||
}
|
||||
|
||||
pub fn update_db_with_file(file: PathBuf) -> bool {
|
||||
let ac = fs::read_to_string(file).unwrap();
|
||||
let ac: Vec<ScryfallCard> = serde_json::from_str(&ac).unwrap();
|
||||
let sqlite_file = get_local_data_sqlite_file();
|
||||
let mut conn = rusqlite::Connection::open(sqlite_file).unwrap();
|
||||
let tx = conn.transaction().unwrap();
|
||||
for card in ac {
|
||||
for word in card.name.split_whitespace() {
|
||||
let word = deunicode(&word.to_lowercase());
|
||||
let res = tx.execute(
|
||||
"INSERT INTO mtg_words (word) VALUES (?1)
|
||||
ON CONFLICT (word) DO NOTHING;",
|
||||
[word.replace(",", "")],
|
||||
);
|
||||
}
|
||||
let lowercase_name = deunicode(&card.name.to_lowercase());
|
||||
let power_toughness = match card.power {
|
||||
Some(p) => Some(format!("{}/{}", p, card.toughness.unwrap())),
|
||||
None => None,
|
||||
};
|
||||
let oracle_text = match card.oracle_text {
|
||||
Some(ot) => ot,
|
||||
None => "<No Oracle Text>".to_string(),
|
||||
};
|
||||
let loyalty = match card.loyalty {
|
||||
Some(loy) => Some(loy),
|
||||
None => None,
|
||||
};
|
||||
let mana_cost = match card.mana_cost {
|
||||
Some(mc) => Some(mc),
|
||||
None => None,
|
||||
};
|
||||
let res = tx.execute(
|
||||
"INSERT INTO cards (name, lowercase_name, type_line, oracle_text, power_toughness, loyalty, mana_cost, scryfall_uri) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
|
||||
rusqlite::params![card.name, lowercase_name, card.type_line, oracle_text, power_toughness, loyalty, mana_cost, card.scryfall_uri],
|
||||
);
|
||||
}
|
||||
tx.commit();
|
||||
true
|
||||
}
|
||||
|
||||
@@ -47,7 +47,6 @@ pub fn download_latest(
|
||||
stype: ScryfallBulkType,
|
||||
mut dest_file: &NamedTempFile,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
panic!();
|
||||
let bulk_body: ScryfallBulk = ureq::get(SCRYFALL_BULK_API)
|
||||
.header("User-Agent", "Arthur's Card Finger Testing v0.1")
|
||||
.header("Accept", "application/json")
|
||||
|
||||
@@ -6,7 +6,8 @@ pub use crate::deser::ScryfallCard;
|
||||
|
||||
mod db;
|
||||
pub use db::{
|
||||
get_all_card_names, get_all_lowercase_card_names, get_card_by_name, init_db,
|
||||
find_matching_cards, find_matching_cards_scryfall_style, get_all_card_names,
|
||||
get_all_lowercase_card_names, get_all_mtg_words, get_card_by_name, init_db,
|
||||
update_db_with_file, GetNameType,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,18 +1,43 @@
|
||||
use clap::Parser;
|
||||
use scryfall_deser::get_all_lowercase_card_names;
|
||||
use scryfall_deser::find_matching_cards_scryfall_style;
|
||||
use scryfall_deser::get_all_mtg_words;
|
||||
use scryfall_deser::get_card_by_name;
|
||||
use scryfall_deser::get_local_cache_folder;
|
||||
use scryfall_deser::init_db;
|
||||
use scryfall_deser::update_db_with_file;
|
||||
use scryfall_deser::GetNameType;
|
||||
use std::process::ExitCode;
|
||||
use std::process::Termination;
|
||||
use textdistance::str::damerau_levenshtein;
|
||||
|
||||
impl Termination for MtgCardExit {
|
||||
fn report(self) -> ExitCode {
|
||||
match self {
|
||||
MtgCardExit::Success => ExitCode::SUCCESS,
|
||||
MtgCardExit::EmptySearchString => ExitCode::from(101),
|
||||
MtgCardExit::NoExactMatchCard => ExitCode::from(102),
|
||||
MtgCardExit::DidYouMean => ExitCode::from(105),
|
||||
MtgCardExit::ExactCardFound => ExitCode::from(200),
|
||||
MtgCardExit::UpdateSuccess => ExitCode::from(201),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum MtgCardExit {
|
||||
Success,
|
||||
UpdateSuccess,
|
||||
ExactCardFound,
|
||||
EmptySearchString,
|
||||
NoExactMatchCard,
|
||||
DidYouMean,
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about, long_about = None)]
|
||||
struct Args {
|
||||
/// Download the latest bulk from Scryfall and update local db
|
||||
/// Update the local db from given Scryfall bulk download
|
||||
#[arg(short, long)]
|
||||
update: bool,
|
||||
update: Option<String>,
|
||||
/// Search for the exact string
|
||||
#[arg(short, long)]
|
||||
exact: bool,
|
||||
@@ -20,78 +45,90 @@ struct Args {
|
||||
search_text: Vec<String>,
|
||||
}
|
||||
|
||||
fn exact_search(search_strings: Vec<String>) {
|
||||
fn exact_search(search_strings: Vec<String>) -> MtgCardExit {
|
||||
let search_string = search_strings.join(" ");
|
||||
let card = get_card_by_name(&search_string, GetNameType::Name);
|
||||
if card.is_none() {
|
||||
panic!("No card found with exact name of {}", search_string);
|
||||
match card {
|
||||
None => {
|
||||
println!("No card found with exact name of {}", search_string);
|
||||
MtgCardExit::NoExactMatchCard
|
||||
}
|
||||
Some(c) => {
|
||||
println!("{}", c);
|
||||
MtgCardExit::ExactCardFound
|
||||
}
|
||||
}
|
||||
println!("{}", card.unwrap());
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args = Args::parse();
|
||||
if args.update {
|
||||
init_db();
|
||||
let mut path = get_local_cache_folder();
|
||||
// TODO - actually download the file - probably do away with TempFile stuff
|
||||
path.push("oracle-cards-20250814210711.json");
|
||||
update_db_with_file(path);
|
||||
return;
|
||||
}
|
||||
if args.search_text.is_empty() {
|
||||
panic!("You need to put some card text to search");
|
||||
}
|
||||
|
||||
if args.exact {
|
||||
exact_search(args.search_text);
|
||||
return;
|
||||
}
|
||||
|
||||
// For use with find_matching_cards
|
||||
fn _combine_search_strings(search_strings: Vec<String>) -> String {
|
||||
let mut search_string = String::new();
|
||||
for card in args.search_text {
|
||||
for card in search_strings {
|
||||
search_string.push_str(&card.to_lowercase());
|
||||
search_string.push_str(" ");
|
||||
}
|
||||
search_string.pop();
|
||||
|
||||
// This section should be replaced with a SQL command
|
||||
let cards = get_all_lowercase_card_names();
|
||||
|
||||
let mut matching_cards = Vec::new();
|
||||
for card in cards {
|
||||
if card.contains(&search_string) {
|
||||
matching_cards.push(card.clone());
|
||||
search_string
|
||||
}
|
||||
|
||||
fn main() -> MtgCardExit {
|
||||
let args = Args::parse();
|
||||
if let Some(update) = args.update {
|
||||
dbg!(&update);
|
||||
init_db();
|
||||
let mut path = get_local_cache_folder();
|
||||
path.push(update);
|
||||
// FIXME - if you pass a bad file or something, it just deletes the db
|
||||
update_db_with_file(path);
|
||||
return MtgCardExit::UpdateSuccess;
|
||||
}
|
||||
if args.search_text.is_empty() {
|
||||
dbg!("You need to put some card text to search");
|
||||
return MtgCardExit::EmptySearchString;
|
||||
}
|
||||
|
||||
if args.exact {
|
||||
let res = exact_search(args.search_text);
|
||||
return res;
|
||||
}
|
||||
|
||||
let mut matching_cards = find_matching_cards_scryfall_style(&args.search_text);
|
||||
dbg!(&args.search_text);
|
||||
dbg!(&matching_cards);
|
||||
|
||||
if matching_cards.is_empty() {
|
||||
// Do some distance checking stuff
|
||||
let mtg_words = get_all_mtg_words();
|
||||
let mut close_names = Vec::new();
|
||||
for search_string in args.search_text {
|
||||
for mtg_card_name in &mtg_words {
|
||||
let dist = damerau_levenshtein(&search_string, &mtg_card_name);
|
||||
if dist <= 2 {
|
||||
close_names.push((dist, mtg_card_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
close_names.sort_by_key(|k| k.0);
|
||||
for (_, card) in close_names {
|
||||
println!("{}", card);
|
||||
}
|
||||
return MtgCardExit::DidYouMean;
|
||||
} else if matching_cards.len() == 1 {
|
||||
let card = get_card_by_name(&matching_cards[0], GetNameType::LowercaseName);
|
||||
dbg!(card);
|
||||
// FIXME - theres a bug in here - try searching Nalf
|
||||
let card = get_card_by_name(&matching_cards[0].name, GetNameType::Name).unwrap();
|
||||
println!("{}", card);
|
||||
// TODO update this to be more meaningful
|
||||
return MtgCardExit::ExactCardFound;
|
||||
} else {
|
||||
matching_cards.sort();
|
||||
for card in matching_cards {
|
||||
println!(
|
||||
"{}",
|
||||
get_card_by_name(&card, GetNameType::LowercaseName)
|
||||
get_card_by_name(&card.lowercase_name, GetNameType::LowercaseName)
|
||||
.unwrap()
|
||||
.name
|
||||
);
|
||||
}
|
||||
// This will be the harder part I think
|
||||
}
|
||||
|
||||
/* For testing - all seemed to work alright
|
||||
let card = get_card_by_name("Black Lotus", GetNameType::Name);
|
||||
dbg!(card);
|
||||
let cards = get_all_card_names();
|
||||
for card in cards {
|
||||
let card = get_card_by_name(&card);
|
||||
dbg!(&card);
|
||||
if card.is_none() {
|
||||
panic!("None card for {:?}", card);
|
||||
// TODO update this to be more meaningful
|
||||
return MtgCardExit::Success;
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user