-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmake_cards.py
executable file
·304 lines (251 loc) · 15.4 KB
/
make_cards.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
#!/usr/bin/env python3
import shutil
from concurrent import futures
from concurrent.futures import ThreadPoolExecutor
import json
import time
from typing import Dict, List, Tuple
import psutil
import asyncio
from .lib import BASE_URL, BUILD_DIR, CARDS_DIR, COLOURS, PROTOCOL, card_path, make_card, CardFontConfig, deck_path, local_file_url, url_to_local_path, clear_deck_path, CONTENT_TEXT_SCALE, TITLE_TEXT_SCALE, IMG_FORMAT
import os
from discord import File
import traceback
import pathlib
def _render_cards(existingFolders, decksFolder, guildID, gameData, cardFont, fontSizes):
expansions, deck_name = gameData["expansions"], gameData["title"]
fonts = CardFontConfig(cardFont, contentFontSize=fontSizes["content"], titleFontSize=fontSizes["title"])
# Clear results directories
try:
shutil.rmtree(os.path.join(deck_path(decksFolder, guildID, deck_name), CARDS_DIR))
shutil.rmtree(os.path.join(deck_path(decksFolder, guildID, deck_name), BUILD_DIR))
except FileNotFoundError:
pass
cardData = {"expansions": {}, "deck_name": deck_name}
def saveCard(args):
try:
if args["expansion"] not in cardData["expansions"]:
cardData["expansions"][args["expansion"]] = {col: [] for col in COLOURS}
cardData["expansions"][args["expansion"]][args["card_type"]].append({"text": args["card_text"], "url":args["file_name"].split(decksFolder)[1].lstrip(os.sep)})
if args["card_type"] == COLOURS[1]:
cardData["expansions"][args["expansion"]][args["card_type"]][-1]["requiredWhiteCards"] = args["card_text"].count("_")
make_card(*args.values())
except Exception as e:
print(traceback.format_exc())
raise e
for expansion_name in expansions:
for colour, cards in zip(COLOURS, (expansions[expansion_name]["white"], expansions[expansion_name]["black"])):
with futures.ThreadPoolExecutor(len(psutil.Process().cpu_affinity())) as executor:
executor.map(
lambda elem: saveCard(elem),
[{
"card_text": c[1],
"file_name": card_path(existingFolders, decksFolder, guildID, deck_name, colour, c[0], expansion=expansion_name),
"fonts": fonts,
"expansion": expansion_name,
"card_type": colour,
"show_small": True,
"game_name": deck_name
} for c in enumerate(cards)],
)
# Create card backs
for colour in COLOURS:
make_card(
deck_name,
card_path(existingFolders, decksFolder, guildID, deck_name, colour, "Back" + colour, root_dir=True),
fonts,
show_small=False,
card_type=colour,
)
cardData[colour + "_back"] = card_path(existingFolders, "", guildID, deck_name, colour, "Back" + colour, root_dir=True)
return cardData
async def render_all(decksFolder, gameData, cardFont, guildID, contentFontSize=CONTENT_TEXT_SCALE, titleFontSize=TITLE_TEXT_SCALE):
deck_name = gameData["title"]
deckDir = deck_path(decksFolder, guildID, deck_name)
if os.path.isdir(deckDir) and os.listdir(deckDir):
raise RuntimeError("deck directory already exists and is not empty: " + deckDir)
eventloop = asyncio.get_event_loop()
existingFolders = dict()
cardData = await eventloop.run_in_executor(ThreadPoolExecutor(), _render_cards, existingFolders, decksFolder, guildID, gameData, cardFont, {"content": contentFontSize, "title": titleFontSize})
return cardData
async def store_cards_discord(decksFolder, cardData, storageChannel, callingMsg):
cardUploaders = set()
async def uploadCard(card, cardPath, msgText):
with open(cardPath, "rb") as f:
cardMsg = await storageChannel.send(msgText, file=File(f))
card["url"] = cardMsg.attachments[0].url
def scheduleCardUpload(card, cardPath, msgText):
task = asyncio.ensure_future(uploadCard(card, cardPath, msgText))
cardUploaders.add(task)
for expansion in cardData["expansions"]:
for colour in cardData["expansions"][expansion]:
for card in cardData["expansions"][expansion][colour]:
cardPath = os.path.join(decksFolder, card["url"].lstrip(os.sep))
scheduleCardUpload(card, cardPath, str(callingMsg.author.id) + "@" + str(callingMsg.guild.id) + "/" + str(callingMsg.channel.id) + "\n" + cardData["deck_name"] + " -> " + expansion + " -> " + card["text"])
for colour in COLOURS:
cardPath = os.path.join(decksFolder, cardData[colour + "_back"])
with open(cardPath, "rb") as f:
cardMsg = await storageChannel.send(str(callingMsg.author.id) + "@" + str(callingMsg.guild.id) + "/" + str(callingMsg.channel.id) + "\n" + cardData["deck_name"] + " -> " + colour + "_back", file=File(f))
cardData[colour + "_back"] = cardMsg.attachments[0].url
if cardUploaders:
await asyncio.wait(cardUploaders)
for t in cardUploaders:
if e := t.exception():
raise e
try:
clear_deck_path(decksFolder, callingMsg.guild.id, cardData["deck_name"])
except FileNotFoundError:
pass
return cardData
def store_cards_local(cardData):
for expansion in cardData["expansions"]:
if len(cardData["expansions"][expansion]["white"]) > 0:
cardData["expansions"][expansion]["dir"] = str(pathlib.Path(cardData["expansions"][expansion]["white"][0]["url"]).parent.parent)
elif len(cardData["expansions"][expansion]["black"]) > 0:
cardData["expansions"][expansion]["dir"] = str(pathlib.Path(cardData["expansions"][expansion]["black"][0]["url"]).parent.parent)
for colour in cardData["expansions"][expansion]:
if colour != "dir":
for card in cardData["expansions"][expansion][colour]:
card["url"] = local_file_url(card["url"])
for colour in COLOURS:
cardData[colour + "_back"] = local_file_url(cardData[colour + "_back"])
return cardData
async def update_deck(decksFolder, oldMeta, newGameData, deckID, cardFont, guildID, emptyExpansions, cardStorageMethod, cardStorageChannel, callingMsg, contentFontSize=CONTENT_TEXT_SCALE, titleFontSize=TITLE_TEXT_SCALE) -> Tuple[dict, Dict[str, List[str]]]:
changeLog: Dict[str, List[str]] = {exp: [] for exp in oldMeta["expansions"]}
changeLog.update({exp: [] for exp in newGameData["expansions"]})
existingFolders = {}
fonts = CardFontConfig(cardFont, contentFontSize=contentFontSize, titleFontSize=titleFontSize)
expansions, deckName = newGameData["expansions"], oldMeta["deck_name"]
expansionsToRemove = [exp for exp in emptyExpansions if exp in oldMeta["expansions"]]
for exp in [exp for exp in oldMeta["expansions"] if exp not in expansions]:
expansionsToRemove.append(exp)
for expansionName in expansionsToRemove:
changeLog[expansionName].append("Expansion deleted")
if "dir" in oldMeta["expansions"][expansionName]:
if os.path.isdir(oldMeta["expansions"][expansionName]["dir"]):
shutil.rmtree(oldMeta["expansions"][expansionName]["dir"])
del oldMeta["expansions"][expansionName]
def saveCard(args):
if args["card_type"] == "dir":
return
try:
if args["expansion"] not in oldMeta["expansions"]:
oldMeta["expansions"][args["expansion"]] = {col: [] for col in COLOURS}
if cardStorageMethod == "local":
oldMeta["expansions"][args["expansion"]]["dir"] = str(pathlib.Path(args["file_name"]).parent.parent)
oldMeta["expansions"][args["expansion"]][args["card_type"]].append({"text": args["card_text"], "url":args["file_name"].split(decksFolder)[min(1, len(args["file_name"].split(decksFolder)) - 1)].lstrip(os.sep)})
if args["card_type"] == COLOURS[1]:
oldMeta["expansions"][args["expansion"]][args["card_type"]][-1]["requiredWhiteCards"] = args["card_text"].count("_")
# print("Added new meta to expansion " + args["expansion"] + ", colour " + args["card_type"] + ":\n",oldMeta["expansions"][args["expansion"]][args["card_type"]][-1])
args["file_name"] = args["file_name"].replace("/", os.sep).lstrip(os.sep)
make_card(*args.values())
except Exception as e:
print("EXCEPT ON CARD TEXT",args["card_text"])
print(traceback.format_exc())
raise e
def oldMetaHasCard(expansionName, colour, cardText):
for card in oldMeta["expansions"][expansionName][colour]:
if card["text"] == cardText:
return True
return False
newExpansions = [exp for exp in expansions if exp not in oldMeta["expansions"]]
for expansionName in newExpansions:
changeLog[expansionName].append("New expansion created")
expansionDir = os.path.join(decksFolder, str(guildID), str(deckID), str(hash(expansionName)))
if os.path.isdir(expansionDir):
shutil.rmtree(expansionDir)
os.makedirs(os.path.join(expansionDir, "white"))
os.makedirs(os.path.join(expansionDir, "black"))
with futures.ThreadPoolExecutor(len(psutil.Process().cpu_affinity())) as executor:
for colour, cards in zip(COLOURS, (expansions[expansionName]["white"], expansions[expansionName]["black"])):
executor.map(
lambda elem: saveCard(elem),
[{
"card_text": c[1],
"file_name": card_path(existingFolders, decksFolder, guildID, deckName, colour, c[0], expansion=expansionName),
"fonts": fonts,
"expansion": expansionName,
"card_type": colour,
"show_small": True,
"game_name": deckName
} for c in enumerate(cards)],
)
for colour in COLOURS:
for card in oldMeta["expansions"][expansionName][colour]:
if cardStorageMethod == "local":
card["url"] = local_file_url(card["url"])
elif cardStorageMethod == "discord":
cardPath = os.path.join(decksFolder, card["url"].lstrip(os.sep))
with open(cardPath, "rb") as f:
cardMsg = await cardStorageChannel.send(str(callingMsg.author.id) + "@" + str(callingMsg.guild.id) + "/" + str(callingMsg.channel.id) + "\n" + deckName + " -> " + expansionName + " -> " + card["text"], file=File(f))
card["url"] = cardMsg.attachments[0].url
else:
raise ValueError("Unsupported cfg.cardStorageMethod: " + str(cardStorageMethod))
for expansionName in [exp for exp in expansions if exp not in newExpansions]:
expansionDir = os.path.join(decksFolder, str(guildID), str(deckID), str(hash(expansionName)))
for colour in oldMeta["expansions"][expansionName]:
if colour == "dir":
continue
cardsToRemove = [cardData for cardData in oldMeta["expansions"][expansionName][colour] if cardData["text"] not in expansions[expansionName][colour]]
if len(cardsToRemove) > 0:
changeLog[expansionName].append(f"-{len(cardsToRemove)} {colour} card{'' if len(cardsToRemove)== 1 else 's'}")
for cardData in cardsToRemove:
imgPath = os.path.join(decksFolder, url_to_local_path(cardData["url"]))
if os.path.isfile(imgPath):
os.remove(imgPath)
oldMeta["expansions"][expansionName][colour].remove(cardData)
async def fixOrAddCardsSet(allCards):
# awful brute force method
if len(oldMeta["expansions"][expansionName][colour]) == 0:
cardNumOffset = 0
else:
cardNumOffset = max(int(c['url'].split("/")[-1][len("card"):-len(IMG_FORMAT)-1]) for c in oldMeta["expansions"][expansionName][colour]) + 1
with futures.ThreadPoolExecutor(len(psutil.Process().cpu_affinity())) as executor:
executor.map(
lambda elem: saveCard(elem),
[{
"card_text": c[1],
"file_name": card_path(existingFolders, decksFolder, guildID, deckName, colour, c[0] + cardNumOffset, expansion=expansionName),
"fonts": fonts,
"expansion": expansionName,
"card_type": colour,
"show_small": True,
"game_name": deckName
} for c in enumerate(allCards)],
)
for cardText in allCards:
card = None
for currentCard in oldMeta["expansions"][expansionName][colour]:
if currentCard["text"] == cardText:
card = currentCard
break
if card is None:
raise RuntimeError("could not find render data for new card : " + cardText)
if cardStorageMethod == "local":
card["url"] = local_file_url(card["url"])
# if card["url"] == f"{PROTOCOL}://{BASE_URL}":
# card["url"] = local_file_url((os.path.join(expansionDir, colour, "card" + str(c[0]) + "." + IMG_FORMAT)))
elif cardStorageMethod == "discord":
cardPath = os.path.join(decksFolder, card["url"].lstrip(os.sep))
with open(cardPath, "rb") as f:
cardMsg = await cardStorageChannel.send(str(callingMsg.author.id) + "@" + str(callingMsg.guild.id) + "/" + str(callingMsg.channel.id) + "\n" + deckName + " -> " + expansionName + " -> " + card["text"], file=File(f))
card["url"] = cardMsg.attachments[0].url
else:
raise ValueError("Unsupported cfg.cardStorageMethod: " + str(cardStorageMethod))
cardsToAdd = [cardText for cardText in expansions[expansionName][colour] if not oldMetaHasCard(expansionName, colour, cardText)]
if len(cardsToAdd) > 0:
changeLog[expansionName].append(f"+{len(cardsToAdd)} {colour} card{'' if len(cardsToAdd)== 1 else 's'}")
await fixOrAddCardsSet(cardsToAdd)
cardsToFix = [cardData for cardData in oldMeta["expansions"][expansionName][colour] if cardData["url"] == f"{PROTOCOL}://{BASE_URL}"]
if len(cardsToFix) > 0:
changeLog[expansionName].append(f"{len(cardsToFix)} empty {colour} card{'' if len(cardsToFix)== 1 else 's'} fixed")
for cardData in cardsToFix:
imgPath = os.path.join(decksFolder, url_to_local_path(cardData["url"]))
if os.path.isfile(imgPath):
os.remove(imgPath)
oldMeta["expansions"][expansionName][colour].remove(cardData)
await fixOrAddCardsSet([c['text'] for c in cardsToFix])
unchangedExpansions = [e for e in changeLog if not changeLog[e]]
for e in unchangedExpansions:
del changeLog[e]
return (oldMeta, changeLog)