Refactor cube API, add buy/pool info
This commit is contained in:
parent
1727882fb5
commit
d0f49fce07
9 changed files with 359 additions and 246 deletions
41
cmd/buy.ts
Normal file
41
cmd/buy.ts
Normal file
|
@ -0,0 +1,41 @@
|
|||
import * as ejs from "ejs";
|
||||
import { existsSync } from "fs";
|
||||
|
||||
import { Article, asyncLoadJSON, CardMarketApi, MCMDB } from "../lib";
|
||||
import { cubeCards, genCube } from "../lib/cube";
|
||||
|
||||
async function run() {
|
||||
if (process.argv.length < 3) {
|
||||
console.error("Usage: yarn buy <uid>");
|
||||
process.exit(1);
|
||||
return;
|
||||
}
|
||||
const uid = process.argv[2];
|
||||
const uidCards = `${uid}-cards.json`;
|
||||
if (!existsSync("mcmCards.json")) {
|
||||
console.error("Card db is missing! Run 'yarn convert-db' first.");
|
||||
process.exit(1);
|
||||
}
|
||||
if (!existsSync(uidCards)) {
|
||||
console.error(`Could not find ${uidCards}! Run 'yarn fetch ${uid}' first.`);
|
||||
process.exit(1);
|
||||
}
|
||||
let db = await asyncLoadJSON<MCMDB>("mcmCards.json");
|
||||
let articles = await asyncLoadJSON<Article[]>(`${uid}-cards.json`);
|
||||
|
||||
const cards = cubeCards(await genCube(db, articles));
|
||||
|
||||
let api = new CardMarketApi();
|
||||
|
||||
// Can only list 100 articles per request
|
||||
let offset = 0;
|
||||
while (offset < cards.length) {
|
||||
const xml: string = await ejs.renderFile("templates/buy.ejs", {
|
||||
cards: cards.slice(offset, offset + 100)
|
||||
});
|
||||
await api.put("/shoppingcart", null, xml);
|
||||
offset += 100;
|
||||
}
|
||||
}
|
||||
|
||||
run();
|
157
cmd/cube-dev.ts
157
cmd/cube-dev.ts
|
@ -1,126 +1,12 @@
|
|||
import * as ejs from "ejs";
|
||||
import { existsSync } from "fs";
|
||||
import { createServer } from "http";
|
||||
|
||||
import {
|
||||
Article,
|
||||
asyncLoadJSON,
|
||||
CardItem,
|
||||
dictMap,
|
||||
filterDict,
|
||||
leanArticle,
|
||||
leanCard,
|
||||
MCMDB,
|
||||
onlyUnique,
|
||||
spanBy,
|
||||
} from "../lib";
|
||||
|
||||
const colorNames = {
|
||||
CL: "Colorless",
|
||||
MC: "Multicolor",
|
||||
L: "Land",
|
||||
W: "White",
|
||||
U: "Blue",
|
||||
B: "Black",
|
||||
R: "Red",
|
||||
G: "Green",
|
||||
WU: "Azorius",
|
||||
UB: "Dimir",
|
||||
BR: "Rakdos",
|
||||
RG: "Gruul",
|
||||
WG: "Selesnya",
|
||||
WB: "Orzhov",
|
||||
UR: "Izzet",
|
||||
BG: "Golgari",
|
||||
WR: "Boros",
|
||||
UG: "Simic",
|
||||
WUG: "Bant",
|
||||
WUB: "Esper",
|
||||
UBR: "Grixis",
|
||||
BRG: "Jund",
|
||||
WRG: "Naya",
|
||||
WBG: "Abzan",
|
||||
WUR: "Jeskai",
|
||||
UBG: "Sultai",
|
||||
WBR: "Mardu",
|
||||
URG: "Temur"
|
||||
};
|
||||
|
||||
const columns: Record<string, (c: CardItem) => boolean> = {
|
||||
W: c => c.types[0] != "Land" && colorid(c.colorIdentity) == "W",
|
||||
U: c => c.types[0] != "Land" && colorid(c.colorIdentity) == "U",
|
||||
B: c => c.types[0] != "Land" && colorid(c.colorIdentity) == "B",
|
||||
R: c => c.types[0] != "Land" && colorid(c.colorIdentity) == "R",
|
||||
G: c => c.types[0] != "Land" && colorid(c.colorIdentity) == "G",
|
||||
MC: c => c.types[0] != "Land" && c.colorIdentity.length > 1,
|
||||
CL: c => c.types[0] != "Land" && colorid(c.colorIdentity) == "CL",
|
||||
L: c => c.types[0] == "Land"
|
||||
};
|
||||
|
||||
function wubrg(a: string, b: string) {
|
||||
const order = ["W", "U", "B", "R", "G"];
|
||||
const indexA = order.indexOf(a);
|
||||
const indexB = order.indexOf(b);
|
||||
return indexA - indexB;
|
||||
}
|
||||
|
||||
function colorid(colors: string[]): string {
|
||||
if (colors.length < 1) {
|
||||
return "CL";
|
||||
}
|
||||
return colors.sort(wubrg).join("");
|
||||
}
|
||||
|
||||
function prettyColor(color: string) {
|
||||
if (color in colorNames) {
|
||||
return colorNames[color];
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
const allTypes = [
|
||||
"Creature",
|
||||
"Planeswalker",
|
||||
"Instant",
|
||||
"Sorcery",
|
||||
"Artifact",
|
||||
"Enchantment"
|
||||
];
|
||||
function typeSort(a: string, b: string): number {
|
||||
const indexA = allTypes.indexOf(a);
|
||||
const indexB = allTypes.indexOf(b);
|
||||
return indexA - indexB;
|
||||
}
|
||||
|
||||
function abcSort(a: string, b: string): number {
|
||||
if (a > b) {
|
||||
return 1;
|
||||
}
|
||||
if (b > a) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// This would be properly typed but as of currently TS does not allow circular references for types
|
||||
function deepSum(dict: any): number {
|
||||
if (Array.isArray(dict)) {
|
||||
return dict.length;
|
||||
}
|
||||
let total = 0;
|
||||
for (let key in dict) {
|
||||
if (Array.isArray(dict[key])) {
|
||||
total += dict[key].length;
|
||||
} else {
|
||||
total += deepSum(dict[key]);
|
||||
}
|
||||
}
|
||||
return total;
|
||||
}
|
||||
import { Article, asyncLoadJSON, MCMDB } from "../lib";
|
||||
import { cubeHTML, genCube } from "../lib/cube";
|
||||
|
||||
async function run() {
|
||||
if (process.argv.length < 3) {
|
||||
console.error("Usage: yarn fetch <uid>");
|
||||
console.error("Usage: yarn cube-dev <uid>");
|
||||
process.exit(1);
|
||||
return;
|
||||
}
|
||||
|
@ -137,43 +23,10 @@ async function run() {
|
|||
let db = await asyncLoadJSON<MCMDB>("mcmCards.json");
|
||||
let articles = await asyncLoadJSON<Article[]>(`${uid}-cards.json`);
|
||||
|
||||
let cards: CardItem[] = articles
|
||||
.filter(art => art.idProduct in db)
|
||||
.map(art => {
|
||||
const card = db[art.idProduct];
|
||||
return {
|
||||
...leanArticle(art),
|
||||
...leanCard(card)
|
||||
};
|
||||
});
|
||||
const cubecards = await genCube(db, articles);
|
||||
|
||||
let valid = cards
|
||||
.filter(
|
||||
c =>
|
||||
(c.language == "Italian" || c.language == "English") &&
|
||||
c.price <= 0.1 &&
|
||||
(c.rarity == "rare" || c.rarity == "mythic")
|
||||
)
|
||||
.filter(onlyUnique);
|
||||
|
||||
// Filter by colors and cmc (nested)
|
||||
let colorcards = filterDict(valid, columns);
|
||||
let cmccards = dictMap(colorcards, (cards, col) =>
|
||||
dictMap(
|
||||
spanBy(cards, c =>
|
||||
col == "MC" || col == "L" ? colorid(c.colorIdentity) : c.types[0]
|
||||
),
|
||||
typed => spanBy(typed, c => c.convertedManaCost)
|
||||
)
|
||||
);
|
||||
const server = createServer(async (req, res) => {
|
||||
const template = await ejs.renderFile("templates/cube.ejs", {
|
||||
user: uid,
|
||||
cards: valid,
|
||||
cmccards,
|
||||
columns,
|
||||
utils: { wubrg, prettyColor, colorid, deepSum, typeSort, abcSort }
|
||||
});
|
||||
const template = await cubeHTML(uid, cubecards);
|
||||
res.end(template);
|
||||
});
|
||||
|
||||
|
|
85
cmd/cube.ts
85
cmd/cube.ts
|
@ -1,64 +1,11 @@
|
|||
import * as ejs from "ejs";
|
||||
import { existsSync, writeFile } from "fs";
|
||||
|
||||
import { Article, asyncLoadJSON, CardItem, leanArticle, leanCard, MCMDB, onlyUnique } from "../lib";
|
||||
|
||||
const colorNames = {
|
||||
CL: "Colorless",
|
||||
W: "White",
|
||||
U: "Blue",
|
||||
B: "Black",
|
||||
R: "Red",
|
||||
G: "Green",
|
||||
WU: "Azorius",
|
||||
UB: "Dimir",
|
||||
BR: "Rakdos",
|
||||
RG: "Gruul",
|
||||
WG: "Selesnya",
|
||||
WB: "Orzhov",
|
||||
UR: "Izzet",
|
||||
BG: "Golgari",
|
||||
WR: "Boros",
|
||||
UG: "Simic",
|
||||
WUG: "Bant",
|
||||
WUB: "Esper",
|
||||
UBR: "Grixis",
|
||||
BRG: "Jund",
|
||||
WRG: "Naya",
|
||||
WBG: "Abzan",
|
||||
WUR: "Jeskai",
|
||||
UBG: "Sultai",
|
||||
WBR: "Mardu",
|
||||
URG: "Temur"
|
||||
};
|
||||
|
||||
const columns: Record<string, (c: CardItem) => boolean> = {
|
||||
W: c => colorid(c.colorIdentity) == "W",
|
||||
U: c => colorid(c.colorIdentity) == "U",
|
||||
B: c => colorid(c.colorIdentity) == "B",
|
||||
R: c => colorid(c.colorIdentity) == "R",
|
||||
G: c => colorid(c.colorIdentity) == "G",
|
||||
MC: c => c.colorIdentity.length > 0,
|
||||
CL: c => colorid(c.colorIdentity) == "CL"
|
||||
};
|
||||
|
||||
function wubrg(a: string, b: string) {
|
||||
const order = ["W", "U", "B", "R", "G"];
|
||||
const indexA = order.indexOf(a);
|
||||
const indexB = order.indexOf(b);
|
||||
return indexA - indexB;
|
||||
}
|
||||
|
||||
function colorid(colors: string[]): string {
|
||||
if (colors.length < 1) {
|
||||
return "CL";
|
||||
}
|
||||
return colors.sort(wubrg).join("");
|
||||
}
|
||||
import { Article, asyncLoadJSON, MCMDB } from "../lib";
|
||||
import { cubeHTML, genCube } from "../lib/cube";
|
||||
|
||||
async function run() {
|
||||
if (process.argv.length < 3) {
|
||||
console.error("Usage: yarn fetch <uid>");
|
||||
console.error("Usage: yarn cube <uid>");
|
||||
process.exit(1);
|
||||
return;
|
||||
}
|
||||
|
@ -75,31 +22,9 @@ async function run() {
|
|||
let db = await asyncLoadJSON<MCMDB>("mcmCards.json");
|
||||
let articles = await asyncLoadJSON<Article[]>(`${uid}-cards.json`);
|
||||
|
||||
let cards: CardItem[] = articles
|
||||
.filter(art => art.idProduct in db)
|
||||
.map(art => {
|
||||
const card = db[art.idProduct];
|
||||
return {
|
||||
...leanArticle(art),
|
||||
...leanCard(card)
|
||||
};
|
||||
});
|
||||
const cubecards = await genCube(db, articles);
|
||||
const template = await cubeHTML(uid, cubecards);
|
||||
|
||||
let valid = cards
|
||||
.filter(
|
||||
c =>
|
||||
(c.language == "Italian" || c.language == "English") &&
|
||||
c.price <= 0.1 &&
|
||||
(c.rarity == "rare" || c.rarity == "mythic")
|
||||
)
|
||||
.filter(onlyUnique);
|
||||
|
||||
const template = await ejs.renderFile("templates/cube.ejs", {
|
||||
user: uid,
|
||||
cards: valid,
|
||||
columns,
|
||||
utils: { wubrg, colorNames, colorid }
|
||||
});
|
||||
let cubeFile = `${uid}-cube.html`;
|
||||
writeFile(cubeFile, template, {}, err => {
|
||||
if (err) {
|
||||
|
|
54
cmd/pool.ts
Normal file
54
cmd/pool.ts
Normal file
|
@ -0,0 +1,54 @@
|
|||
import { existsSync } from "fs";
|
||||
|
||||
import { Article, asyncLoadJSON, CardItem, MCMDB } from "../lib";
|
||||
import { cubeCards, genCube } from "../lib/cube";
|
||||
|
||||
async function run() {
|
||||
if (process.argv.length < 3) {
|
||||
console.error("Usage: yarn buy <uid>");
|
||||
process.exit(1);
|
||||
return;
|
||||
}
|
||||
const uid = process.argv[2];
|
||||
const uidCards = `${uid}-cards.json`;
|
||||
if (!existsSync("mcmCards.json")) {
|
||||
console.error("Card db is missing! Run 'yarn convert-db' first.");
|
||||
process.exit(1);
|
||||
}
|
||||
if (!existsSync(uidCards)) {
|
||||
console.error(`Could not find ${uidCards}! Run 'yarn fetch ${uid}' first.`);
|
||||
process.exit(1);
|
||||
}
|
||||
let db = await asyncLoadJSON<MCMDB>("mcmCards.json");
|
||||
let articles = await asyncLoadJSON<Article[]>(`${uid}-cards.json`);
|
||||
|
||||
const cards = cubeCards(await genCube(db, articles));
|
||||
|
||||
let types: Record<string, CardItem[]> = {};
|
||||
cards.forEach(c => {
|
||||
c.subtypes.forEach(t => {
|
||||
if (t in types) {
|
||||
types[t].push(c);
|
||||
} else {
|
||||
types[t] = [c];
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
console.log("TRIBAL? Top 10 tribes:");
|
||||
const entries = Object.entries(types)
|
||||
.sort((a, b) => b[1].length - a[1].length)
|
||||
.slice(0, 10)
|
||||
.forEach(([typ, typcards]) => {
|
||||
console.log(
|
||||
` ${typ.padEnd(16, " ")}${typcards.length
|
||||
.toString()
|
||||
.padStart(3)} cards (${(
|
||||
(typcards.length / cards.length) *
|
||||
100
|
||||
).toFixed(1)}%)`
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
run();
|
54
lib/api.ts
54
lib/api.ts
|
@ -1,5 +1,5 @@
|
|||
import { stringify } from "querystring";
|
||||
import { get } from "request";
|
||||
import * as request from "request";
|
||||
|
||||
import { Article } from "./types";
|
||||
|
||||
|
@ -18,28 +18,39 @@ export class CardMarketApi {
|
|||
this.AccessSecret = process.env.ACCESS_SECRET;
|
||||
}
|
||||
|
||||
async get(path: string, params?: Record<string, any>): Promise<any> {
|
||||
const oauthParameters = {
|
||||
async request(
|
||||
method: string,
|
||||
path: string,
|
||||
params: Record<string, any> | undefined,
|
||||
options: any
|
||||
): Promise<any> {
|
||||
const oauth = {
|
||||
realm: `${URI}${path}`,
|
||||
consumer_key: this.AppToken,
|
||||
consumer_secret: this.AppSecret,
|
||||
token: this.AccessToken,
|
||||
token_secret: this.AccessSecret
|
||||
};
|
||||
const uri = oauth.realm + (params ? `?${stringify(params)}` : "");
|
||||
return new Promise((resolve, reject) => {
|
||||
get(
|
||||
oauthParameters.realm + (params ? `?${stringify(params)}` : ""),
|
||||
request(
|
||||
{
|
||||
oauth: oauthParameters
|
||||
method,
|
||||
uri,
|
||||
oauth,
|
||||
...options
|
||||
},
|
||||
(error, response) => {
|
||||
if (error) {
|
||||
throw error;
|
||||
reject(error);
|
||||
} else if (response.statusCode > 299) {
|
||||
throw JSON.stringify({
|
||||
statusCode: response.statusCode,
|
||||
statusMessage: response.statusMessage
|
||||
});
|
||||
reject(
|
||||
JSON.stringify({
|
||||
statusCode: response.statusCode,
|
||||
statusMessage: response.statusMessage,
|
||||
statusBody: response.body
|
||||
})
|
||||
);
|
||||
}
|
||||
try {
|
||||
resolve(JSON.parse(response.body));
|
||||
|
@ -51,6 +62,27 @@ export class CardMarketApi {
|
|||
});
|
||||
}
|
||||
|
||||
async get(path: string, params?: Record<string, any>): Promise<any> {
|
||||
return await this.request("GET", path, params, {});
|
||||
}
|
||||
|
||||
async put(
|
||||
path: string,
|
||||
params?: Record<string, any>,
|
||||
data?: string
|
||||
): Promise<any> {
|
||||
return await this.request(
|
||||
"PUT",
|
||||
path,
|
||||
params,
|
||||
data
|
||||
? {
|
||||
body: data
|
||||
}
|
||||
: {}
|
||||
);
|
||||
}
|
||||
|
||||
async getAllArticles(uid: string): Promise<Article[]> {
|
||||
const perPage = 1000;
|
||||
let start = 0;
|
||||
|
|
189
lib/cube.ts
Normal file
189
lib/cube.ts
Normal file
|
@ -0,0 +1,189 @@
|
|||
import * as ejs from "ejs";
|
||||
|
||||
import { Article, CardItem, dictMap, filterDict, leanArticle, leanCard, MCMDB, onlyUnique, spanBy } from ".";
|
||||
|
||||
const colorNames = {
|
||||
CL: "Colorless",
|
||||
MC: "Multicolor",
|
||||
L: "Land",
|
||||
W: "White",
|
||||
U: "Blue",
|
||||
B: "Black",
|
||||
R: "Red",
|
||||
G: "Green",
|
||||
WU: "Azorius",
|
||||
UB: "Dimir",
|
||||
BR: "Rakdos",
|
||||
RG: "Gruul",
|
||||
WG: "Selesnya",
|
||||
WB: "Orzhov",
|
||||
UR: "Izzet",
|
||||
BG: "Golgari",
|
||||
WR: "Boros",
|
||||
UG: "Simic",
|
||||
WUG: "Bant",
|
||||
WUB: "Esper",
|
||||
UBR: "Grixis",
|
||||
BRG: "Jund",
|
||||
WRG: "Naya",
|
||||
WBG: "Abzan",
|
||||
WUR: "Jeskai",
|
||||
UBG: "Sultai",
|
||||
WBR: "Mardu",
|
||||
URG: "Temur"
|
||||
};
|
||||
|
||||
const columns: Record<string, (c: CardItem) => boolean> = {
|
||||
W: c => colorid(c.colorIdentity) == "W",
|
||||
U: c => colorid(c.colorIdentity) == "U",
|
||||
B: c => colorid(c.colorIdentity) == "B",
|
||||
R: c => colorid(c.colorIdentity) == "R",
|
||||
G: c => colorid(c.colorIdentity) == "G",
|
||||
MC: c => c.colorIdentity.length > 1,
|
||||
CL: c => colorid(c.colorIdentity) == "CL"
|
||||
};
|
||||
|
||||
function wubrg(a: string, b: string) {
|
||||
const order = ["W", "U", "B", "R", "G"];
|
||||
const indexA = order.indexOf(a);
|
||||
const indexB = order.indexOf(b);
|
||||
return indexA - indexB;
|
||||
}
|
||||
|
||||
function colorid(colors: string[]): string {
|
||||
if (colors.length < 1) {
|
||||
return "CL";
|
||||
}
|
||||
return colors.sort(wubrg).join("");
|
||||
}
|
||||
|
||||
function prettyColor(color: string) {
|
||||
if (color in colorNames) {
|
||||
return colorNames[color];
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
const allTypes = [
|
||||
"Creature",
|
||||
"Planeswalker",
|
||||
"Instant",
|
||||
"Sorcery",
|
||||
"Artifact",
|
||||
"Enchantment"
|
||||
];
|
||||
function typeSort(a: string, b: string): number {
|
||||
const indexA = allTypes.indexOf(a);
|
||||
const indexB = allTypes.indexOf(b);
|
||||
return indexA - indexB;
|
||||
}
|
||||
|
||||
function abcSort(a: string, b: string): number {
|
||||
if (a > b) {
|
||||
return 1;
|
||||
}
|
||||
if (b > a) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// This would be properly typed but as of currently TS does not allow circular references for types
|
||||
function deepSum(dict: any): number {
|
||||
if (Array.isArray(dict)) {
|
||||
return dict.length;
|
||||
}
|
||||
let total = 0;
|
||||
for (let key in dict) {
|
||||
if (Array.isArray(dict[key])) {
|
||||
total += dict[key].length;
|
||||
} else {
|
||||
total += deepSum(dict[key]);
|
||||
}
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
export function cubeCards(c: CubeCards): CardItem[] {
|
||||
let cards: CardItem[] = [];
|
||||
dictMap(c, bytyp =>
|
||||
dictMap(bytyp, bycmc =>
|
||||
dictMap(bycmc, cardlist => (cards = cards.concat(cardlist)))
|
||||
)
|
||||
);
|
||||
return cards;
|
||||
}
|
||||
|
||||
function byRank(a: CardItem, b: CardItem): number {
|
||||
return a.edhrecRank - b.edhrecRank;
|
||||
}
|
||||
|
||||
export type ByCMC<T> = Record<number, T>;
|
||||
export type ByType<T> = Record<string, T>;
|
||||
export type ByColor<T> = Record<string, T>;
|
||||
export type CubeCards = ByColor<ByType<ByCMC<CardItem[]>>>;
|
||||
|
||||
export async function genCube(
|
||||
db: MCMDB,
|
||||
articles: Article[]
|
||||
): Promise<CubeCards> {
|
||||
const cards: CardItem[] = articles
|
||||
.filter(art => art.idProduct in db)
|
||||
.map(art => {
|
||||
const card = db[art.idProduct];
|
||||
return {
|
||||
...leanArticle(art),
|
||||
...leanCard(card)
|
||||
};
|
||||
});
|
||||
|
||||
const valid = cards
|
||||
.filter(
|
||||
c =>
|
||||
(c.language == "Italian" || c.language == "English") &&
|
||||
c.price < 0.2 &&
|
||||
(c.rarity == "rare" || c.rarity == "mythic")
|
||||
)
|
||||
.filter(onlyUnique);
|
||||
|
||||
const categorized = filterDict(valid, columns);
|
||||
|
||||
// Cut cards
|
||||
const filtered = dictMap(categorized, (pool, col) => {
|
||||
switch (col) {
|
||||
case "MC":
|
||||
const colors = spanBy(pool, c => colorid(c.colorIdentity));
|
||||
let cards = [];
|
||||
for (const color in colors) {
|
||||
cards = cards.concat(colors[color].sort(byRank).slice(0, 3));
|
||||
}
|
||||
return cards;
|
||||
case "CL":
|
||||
return pool.sort(byRank).slice(0, 30);
|
||||
default:
|
||||
return pool.sort(byRank).slice(0, 58);
|
||||
}
|
||||
});
|
||||
|
||||
// Filter by colors and cmc (nested)
|
||||
return dictMap(filtered, (cards, col) =>
|
||||
dictMap(
|
||||
spanBy(cards, c =>
|
||||
col == "MC" || col == "L"
|
||||
? colorid(c.colorIdentity)
|
||||
: c.types.slice(-1)[0]
|
||||
),
|
||||
typed => spanBy(typed, c => c.convertedManaCost)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export async function cubeHTML(user, cubecards: CubeCards) {
|
||||
return await ejs.renderFile("templates/cube.ejs", {
|
||||
user,
|
||||
cards: cubeCards(cubecards),
|
||||
cubecards,
|
||||
columns,
|
||||
utils: { wubrg, prettyColor, colorid, deepSum, typeSort, abcSort }
|
||||
});
|
||||
}
|
|
@ -10,7 +10,9 @@
|
|||
"build-db": "ts-node cmd/convert.ts",
|
||||
"find": "ts-node cmd/find.ts",
|
||||
"cube": "ts-node cmd/cube.ts",
|
||||
"cube-watch": "ts-node cmd/cube-dev.ts"
|
||||
"cube-watch": "ts-node cmd/cube-dev.ts",
|
||||
"buy": "ts-node cmd/buy.ts",
|
||||
"pool-info": "ts-node cmd/pool.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/ejs": "^2.6.3",
|
||||
|
|
10
templates/buy.ejs
Normal file
10
templates/buy.ejs
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<request>
|
||||
<action>add</action>
|
||||
<% cards.forEach(card => { %>
|
||||
<article>
|
||||
<idArticle><%= card.idArticle %></idArticle>
|
||||
<amount>1</amount>
|
||||
</article>
|
||||
<% }); %>
|
||||
</request>
|
|
@ -2,6 +2,7 @@
|
|||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Junk rare cube</title>
|
||||
<style>
|
||||
body {
|
||||
|
@ -135,6 +136,11 @@
|
|||
width: auto;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
small {
|
||||
color: #aaa;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
|
@ -144,9 +150,10 @@
|
|||
<h1>Junk rare cube (<%=cards.length%> cards)</h1>
|
||||
<h2>Using cards from <a href="https://www.cardmarket.com/en/Magic/Users/<%=user%>"><b><%= user %></b></a>
|
||||
</h2>
|
||||
<h2>Est. price without shipping: € <%= cards.reduce((a, c) => a + c.price, 0).toFixed(2) %></h2>
|
||||
</header>
|
||||
<section class="columns">
|
||||
<% Object.entries(cmccards).forEach(([color, colorcards]) => { %>
|
||||
<% Object.entries(cubecards).forEach(([color, colorcards]) => { %>
|
||||
<section class="color-section color-<%=color%>">
|
||||
<header class="color-header">
|
||||
<h3><%=utils.prettyColor(color)%> (<%=utils.deepSum(colorcards)%>)</h3>
|
||||
|
@ -163,7 +170,7 @@
|
|||
<li data-set="<%=card.set%>" data-num="<%=card.number%>">
|
||||
<a target="_blank" data-image="<%=card.scryfallImageUrl%>"
|
||||
href="<%= card.scryfallUrl %>">
|
||||
<%= card.name %>
|
||||
<%= card.name %><small>€ <%=card.price.toFixed(2)%> - #<%=card.edhrecRank%></small>
|
||||
</a>
|
||||
</li>
|
||||
<% }); %>
|
||||
|
|
Loading…
Reference in a new issue