Merge pull request 'card-creator' (#66) from card-creator into dev

Reviewed-on: #66
This commit is contained in:
waifu 2025-07-17 20:46:57 -07:00
commit 86de43678c
6 changed files with 123 additions and 6 deletions

View file

@ -22,6 +22,10 @@ cd "$(dirname "$0")"
# Activate virtual environment
source venv/bin/activate
# Read the environment mode
ENV_MODE=${KEMOVERSE_ENV:-prod}
echo "Running in '$ENV_MODE' mode..."
# Start the bot
echo "Starting bot..."
python3 bot/bot_app.py &
@ -31,7 +35,11 @@ BOT_PID=$!
# Start the website
echo "Starting web server..."
python3 web/app.py &
if [ "$ENV_MODE" = "dev" ]; then
python3 web/app.py &
else
gunicorn -w 4 -b 127.0.0.1:8000 web.wsgi:app &
fi
# Save the web PID too
WEB_PID=$!

0
web/__init__.py Normal file
View file

View file

@ -28,7 +28,7 @@ from werkzeug.exceptions import HTTPException
app = Flask(__name__)
def get_db_connection():
conn = sqlite3.connect(config.DB_PATH)
conn = sqlite3.connect(config.DB_PATH, check_same_thread=False)
conn.row_factory = sqlite3.Row
return conn
@ -108,5 +108,9 @@ def player_list():
return render_template('player_list.html', players=[])
return render_template('player_list.html', players=players)
if __name__ == '__main__':
app.run(host=config.BIND_ADDRESS, port=config.WEB_PORT, debug=True)
if __name__ == '__main__' and os.environ.get("KEMOVERSE_ENV") == "dev":
app.run(
host=config.BIND_ADDRESS,
port=config.WEB_PORT,
debug=True,
)

13
web/static/js/jszip.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -64,6 +64,7 @@
</div>
<script src="{{ url_for('static', filename='js/jszip.min.js') }}"></script>
<script>
const canvas = document.getElementById("cardCanvas");
const ctx = canvas.getContext("2d");
@ -188,7 +189,7 @@ function drawCard() {
async function drawCardId() {
const id = await generateCardId(packInput.value, nameInput.value, powerInput.value, charmInput.value, witInput.value, artistInput.value);
ctx.font = "15px sans-serif";
ctx.fillText("KC-" + id, canvas.width/2, canvas.height - 30);
ctx.fillText("Kemoverse - " + id, canvas.width/2, canvas.height - 30);
}
@ -221,10 +222,83 @@ function wrapText(text, x, y, maxWidth, lineHeight) {
// Download button
downloadBtn.addEventListener("click", () => {
// Sanitize file name
const safeName = (nameInput.value || "card").replace(/[^a-z0-9_\-]/gi, "_");
const safePack = (packInput.value || "pack").replace(/[^a-z0-9_\-]/gi, "_");
const fileName = `kemoverse_${safePack}_${safeName}.webp`;
const link = document.createElement("a");
link.download = "kemoverse-card.webp";
link.download = fileName;
link.href = canvas.toDataURL("image/webp", 0.95);
link.click();
});
const jsonBtn = document.createElement("button");
jsonBtn.textContent = "Download Card Info";
jsonBtn.style.marginTop = "0.5rem";
jsonBtn.onclick = () => {
const cardData = {
name: nameInput.value,
pack: packInput.value,
power: powerInput.value,
charm: charmInput.value,
wit: witInput.value,
flavor: flavorInput.value,
artist: artistInput.value,
frame: frameSelect.value,
id: generateCardId(packInput.value, nameInput.value, powerInput.value, charmInput.value, witInput.value, artistInput.value)
};
const safeName = (nameInput.value || "card").replace(/[^a-z0-9_\-]/gi, "_");
const safePack = (packInput.value || "pack").replace(/[^a-z0-9_\-]/gi, "_");
const fileName = `kemoverse_${safePack}_${safeName}.json`;
const blob = new Blob([JSON.stringify(cardData, null, 2)], {type: "application/json"});
const link = document.createElement("a");
link.href = URL.createObjectURL(blob);
link.download = fileName;
link.click();
};
// Place the button below the downloadBtn
downloadBtn.parentNode.insertBefore(jsonBtn, downloadBtn.nextSibling);
const zipBtn = document.createElement("button");
zipBtn.textContent = "Download Card ZIP";
zipBtn.style.marginTop = "0.5rem";
zipBtn.onclick = async () => {
const cardData = {
name: nameInput.value,
pack: packInput.value,
power: powerInput.value,
charm: charmInput.value,
wit: witInput.value,
flavor: flavorInput.value,
artist: artistInput.value,
frame: frameSelect.value,
id: await generateCardId(packInput.value, nameInput.value, powerInput.value, charmInput.value, witInput.value, artistInput.value)
};
const safeName = (nameInput.value || "card").replace(/[^a-z0-9_\-]/gi, "_");
const safePack = (packInput.value || "pack").replace(/[^a-z0-9_\-]/gi, "_");
const imgFileName = `kemoverse_${safePack}_${safeName}.webp`;
const jsonFileName = `kemoverse_${safePack}_${safeName}.json`;
// Create ZIP
const zip = new JSZip();
// Add image
const imgData = canvas.toDataURL("image/webp", 0.95).split(',')[1];
zip.file(imgFileName, imgData, {base64: true});
// Add JSON
zip.file(jsonFileName, JSON.stringify(cardData, null, 2));
// Generate and download
const content = await zip.generateAsync({type: "blob"});
const link = document.createElement("a");
link.href = URL.createObjectURL(content);
link.download = `kemoverse_${safePack}_${safeName}.zip`;
link.click();
};
// Place the button below the downloadBtn
downloadBtn.parentNode.insertBefore(zipBtn, downloadBtn.nextSibling);
</script>
{% endblock %}

18
web/wsgi.py Normal file
View file

@ -0,0 +1,18 @@
#Kemoverse - a gacha-style bot for the Fediverse.
#Copyright © 2025 Waifu
#
#This program is free software: you can redistribute it and/or modify
#it under the terms of the GNU Affero General Public License as
#published by the Free Software Foundation, either version 3 of the
#License, or (at your option) any later version.
#
#This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#GNU Affero General Public License for more details.
#
#You should have received a copy of the GNU Affero General Public License
#along with this program. If not, see https://www.gnu.org/licenses/.
from .app import app