diff --git a/.env.sample b/.env.sample deleted file mode 100644 index a53645f..0000000 --- a/.env.sample +++ /dev/null @@ -1,36 +0,0 @@ -APP_ID= -DISCORD_TOKEN= -PUBLIC_KEY= - -# Server, used only for chaos timer -GUILD_ID= - -# Role that can vote, only people with this role will be considered to calculate the majority -VOTING_ROLE_ID= - -# Polls max up time, in seconds -# default: 300 (5 minutes) -POLL_TIME=300 - -# Minimum votes needed, active if greater than majority -# default: 2 (prevents votes needed to be 1 if majority == 1) -MIN_VOTES=2 - -# Probability of quoi -> feur, between 0 and 1 -# default : 0.3 (30% chance of Flopobot saying 'feur' when someone says 'quoi') -FEUR_PROB=0.3 - -# Probability of triggering the chaos timer at midnight, between 0 and 1 -# default : 0.1 (10% chance of Flopobot picking randomely a user to time out for 12 hours at midnight) -CHAOS_PROB=0.1 - -# Everyone can be picked at the chaos timer -# default : false (Only VOTING_ROLE_ID users can be picked) -EVERYONE_IN_CHAOS=false - -# Every time the death roulette, cron expression -# default : '0 0 * * *' (Every day at midnight) -#CRON_EXPR='*/1 * * * * *' -CRON_EXPR='0 0 * * *' - -OPENAI_API_KEY= \ No newline at end of file diff --git a/.gitignore b/.gitignore index 19b7bea..0fc94da 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ node_modules flopobot.db flopobot.db-shm flopobot.db-wal +.idea +flopobot_bc.db diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 13566b8..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml deleted file mode 100644 index f739e7f..0000000 --- a/.idea/codeStyles/Project.xml +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml deleted file mode 100644 index 79ee123..0000000 --- a/.idea/codeStyles/codeStyleConfig.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml deleted file mode 100644 index 2756b5d..0000000 --- a/.idea/dataSources.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - sqlite.xerial - true - org.sqlite.JDBC - jdbc:sqlite:C:\Users\milog\coding\bot_discord\flopobot_v2\flopobot.db - $ProjectFileDir$ - - - \ No newline at end of file diff --git a/.idea/flopobot_v2.iml b/.idea/flopobot_v2.iml deleted file mode 100644 index c956989..0000000 --- a/.idea/flopobot_v2.iml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index 03d9549..0000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/jsLibraryMappings.xml b/.idea/jsLibraryMappings.xml deleted file mode 100644 index d23208f..0000000 --- a/.idea/jsLibraryMappings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/jsLinters/eslint.xml b/.idea/jsLinters/eslint.xml deleted file mode 100644 index 541945b..0000000 --- a/.idea/jsLinters/eslint.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/laravel-idea.xml b/.idea/laravel-idea.xml deleted file mode 100644 index bd941a4..0000000 --- a/.idea/laravel-idea.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 36054e9..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/php.xml b/.idea/php.xml deleted file mode 100644 index 42d41d7..0000000 --- a/.idea/php.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/prettier.xml b/.idea/prettier.xml deleted file mode 100644 index 0c83ac4..0000000 --- a/.idea/prettier.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml deleted file mode 100644 index c0e01ca..0000000 --- a/.idea/sqldialects.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/flopobot.db b/flopobot.db new file mode 100644 index 0000000..fa11a41 Binary files /dev/null and b/flopobot.db differ diff --git a/src/bot/commands/floposite.js b/src/bot/commands/floposite.js index 0f4db3a..5a009f9 100644 --- a/src/bot/commands/floposite.js +++ b/src/bot/commands/floposite.js @@ -8,7 +8,7 @@ import { InteractionResponseType, MessageComponentTypes, ButtonStyleTypes } from */ export async function handleFlopoSiteCommand(req, res) { // The URL for the link button. Consider moving to .env if it changes. - const siteUrl = process.env.FLOPOSITE_URL || "https://floposite.com"; + const siteUrl = process.env.FLOPOSITE_URL; // The URL for the thumbnail image. const thumbnailUrl = `${process.env.API_URL}/public/images/flopo.png`; diff --git a/src/database/index.js b/src/database/index.js index 47fbf75..0979c10 100644 --- a/src/database/index.js +++ b/src/database/index.js @@ -2,6 +2,123 @@ import Database from "better-sqlite3"; export const flopoDB = new Database("flopobot.db"); +/* ------------------------- + CREATE ALL TABLES FIRST +----------------------------*/ +flopoDB.exec(` + CREATE TABLE IF NOT EXISTS users + ( + id TEXT PRIMARY KEY, + username TEXT NOT NULL, + globalName TEXT, + warned BOOLEAN DEFAULT 0, + warns INTEGER DEFAULT 0, + allTimeWarns INTEGER DEFAULT 0, + totalRequests INTEGER DEFAULT 0, + coins INTEGER DEFAULT 0, + dailyQueried BOOLEAN DEFAULT 0, + avatarUrl TEXT DEFAULT NULL, + isAkhy BOOLEAN DEFAULT 0 + ); + + CREATE TABLE IF NOT EXISTS skins + ( + uuid TEXT PRIMARY KEY, + displayName TEXT, + contentTierUuid TEXT, + displayIcon TEXT, + user_id TEXT REFERENCES users, + tierRank TEXT, + tierColor TEXT, + tierText TEXT, + basePrice TEXT, + currentLvl INTEGER DEFAULT NULL, + currentChroma INTEGER DEFAULT NULL, + currentPrice INTEGER DEFAULT NULL, + maxPrice INTEGER DEFAULT NULL + ); + + CREATE TABLE IF NOT EXISTS market_offers + ( + id PRIMARY KEY, + skin_uuid TEXT REFERENCES skins, + seller_id TEXT REFERENCES users, + starting_price INTEGER NOT NULL, + buyout_price INTEGER DEFAULT NULL, + final_price INTEGER DEFAULT NULL, + status TEXT NOT NULL, + posted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + opening_at TIMESTAMP NOT NULL, + closing_at TIMESTAMP NOT NULL, + buyer_id TEXT REFERENCES users DEFAULT NULL + ); + + CREATE TABLE IF NOT EXISTS bids + ( + id PRIMARY KEY, + bidder_id TEXT REFERENCES users, + market_offer_id REFERENCES market_offers, + offer_amount INTEGER, + offered_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + + CREATE TABLE IF NOT EXISTS logs + ( + id PRIMARY KEY, + user_id TEXT REFERENCES users, + action TEXT, + target_user_id TEXT REFERENCES users, + coins_amount INTEGER, + user_new_amount INTEGER, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + + CREATE TABLE IF NOT EXISTS games + ( + id PRIMARY KEY, + p1 TEXT REFERENCES users, + p2 TEXT REFERENCES users, + p1_score INTEGER, + p2_score INTEGER, + p1_elo INTEGER, + p2_elo INTEGER, + p1_new_elo INTEGER, + p2_new_elo INTEGER, + type TEXT, + timestamp TIMESTAMP + ); + + CREATE TABLE IF NOT EXISTS elos + ( + id PRIMARY KEY REFERENCES users, + elo INTEGER + ); + + CREATE TABLE IF NOT EXISTS sotd + ( + id INT PRIMARY KEY, + tableauPiles TEXT, + foundationPiles TEXT, + stockPile TEXT, + wastePile TEXT, + isDone BOOLEAN DEFAULT false, + seed TEXT + ); + + CREATE TABLE IF NOT EXISTS sotd_stats + ( + id TEXT PRIMARY KEY, + user_id TEXT REFERENCES users, + time INTEGER, + moves INTEGER, + score INTEGER + ); +`); + +/* ----------------------------------------------------- + PREPARE ANY CREATE TABLE STATEMENT OBJECTS (kept for parity) +------------------------------------------------------*/ + export const stmtUsers = flopoDB.prepare(` CREATE TABLE IF NOT EXISTS users ( @@ -19,6 +136,7 @@ export const stmtUsers = flopoDB.prepare(` ) `); stmtUsers.run(); + export const stmtSkins = flopoDB.prepare(` CREATE TABLE IF NOT EXISTS skins ( @@ -39,6 +157,106 @@ export const stmtSkins = flopoDB.prepare(` `); stmtSkins.run(); +export const stmtMarketOffers = flopoDB.prepare(` + CREATE TABLE IF NOT EXISTS market_offers + ( + id PRIMARY KEY, + skin_uuid TEXT REFERENCES skins, + seller_id TEXT REFERENCES users, + starting_price INTEGER NOT NULL, + buyout_price INTEGER DEFAULT NULL, + final_price INTEGER DEFAULT NULL, + status TEXT NOT NULL, + posted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + opening_at TIMESTAMP NOT NULL, + closing_at TIMESTAMP NOT NULL, + buyer_id TEXT REFERENCES users DEFAULT NULL + ) +`); +stmtMarketOffers.run(); + +export const stmtBids = flopoDB.prepare(` + CREATE TABLE IF NOT EXISTS bids + ( + id PRIMARY KEY, + bidder_id TEXT REFERENCES users, + market_offer_id REFERENCES market_offers, + offer_amount INTEGER, + offered_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) +`); +stmtBids.run(); + +export const stmtLogs = flopoDB.prepare(` + CREATE TABLE IF NOT EXISTS logs + ( + id PRIMARY KEY, + user_id TEXT REFERENCES users, + action TEXT, + target_user_id TEXT REFERENCES users, + coins_amount INTEGER, + user_new_amount INTEGER, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) +`); +stmtLogs.run(); + +export const stmtGames = flopoDB.prepare(` + CREATE TABLE IF NOT EXISTS games + ( + id PRIMARY KEY, + p1 TEXT REFERENCES users, + p2 TEXT REFERENCES users, + p1_score INTEGER, + p2_score INTEGER, + p1_elo INTEGER, + p2_elo INTEGER, + p1_new_elo INTEGER, + p2_new_elo INTEGER, + type TEXT, + timestamp TIMESTAMP + ) +`); +stmtGames.run(); + +export const stmtElos = flopoDB.prepare(` + CREATE TABLE IF NOT EXISTS elos + ( + id PRIMARY KEY REFERENCES users, + elo INTEGER + ) +`); +stmtElos.run(); + +export const stmtSOTD = flopoDB.prepare(` + CREATE TABLE IF NOT EXISTS sotd + ( + id INT PRIMARY KEY, + tableauPiles TEXT, + foundationPiles TEXT, + stockPile TEXT, + wastePile TEXT, + isDone BOOLEAN DEFAULT false, + seed TEXT + ) +`); +stmtSOTD.run(); + +export const stmtSOTDStats = flopoDB.prepare(` + CREATE TABLE IF NOT EXISTS sotd_stats + ( + id TEXT PRIMARY KEY, + user_id TEXT REFERENCES users, + time INTEGER, + moves INTEGER, + score INTEGER + ) +`); +stmtSOTDStats.run(); + +/* ------------------------- + USER statements +----------------------------*/ export const insertUser = flopoDB.prepare( `INSERT INTO users (id, username, globalName, warned, warns, allTimeWarns, totalRequests, avatarUrl, isAkhy) VALUES (@id, @username, @globalName, @warned, @warns, @allTimeWarns, @totalRequests, @avatarUrl, @isAkhy)`, @@ -68,6 +286,9 @@ export const getAllAkhys = flopoDB.prepare( "SELECT users.*,elos.elo FROM users LEFT JOIN elos ON elos.id = users.id WHERE isAkhy = 1 ORDER BY coins DESC", ); +/* ------------------------- + SKINS statements +----------------------------*/ export const insertSkin = flopoDB.prepare( `INSERT INTO skins (uuid, displayName, contentTierUuid, displayIcon, user_id, tierRank, tierColor, tierText, basePrice, currentLvl, currentChroma, currentPrice, maxPrice) @@ -106,36 +327,9 @@ export const getUserInventory = flopoDB.prepare( ); export const getTopSkins = flopoDB.prepare("SELECT * FROM skins ORDER BY maxPrice DESC LIMIT 10"); -export const stmtMarketOffers = flopoDB.prepare(` - CREATE TABLE IF NOT EXISTS market_offers - ( - id PRIMARY KEY, - skin_uuid TEXT REFERENCES skins, - seller_id TEXT REFERENCES users, - starting_price INTEGER NOT NULL, - buyout_price INTEGER DEFAULT NULL, - final_price INTEGER DEFAULT NULL, - status TEXT NOT NULL, - posted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - opening_at TIMESTAMP NOT NULL, - closing_at TIMESTAMP NOT NULL, - buyer_id TEXT REFERENCES users DEFAULT NULL - ) -`); -stmtMarketOffers.run(); - -export const stmtBids = flopoDB.prepare(` - CREATE TABLE IF NOT EXISTS bids - ( - id PRIMARY KEY, - bidder_id TEXT REFERENCES users, - market_offer_id REFERENCES market_offers, - offer_amount INTEGER, - offered_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP - ) -`); -stmtBids.run(); - +/* ------------------------- + MARKET / BIDS / OFFERS +----------------------------*/ export const getMarketOffers = flopoDB.prepare(` SELECT * FROM market_offers @@ -177,6 +371,9 @@ export const insertMarketOffer = flopoDB.prepare(` VALUES (@id, @skin_uuid, @seller_id, @starting_price, @buyout_price, @status, @opening_at, @closing_at) `); +/* ------------------------- + BIDS +----------------------------*/ export const getBids = flopoDB.prepare(` SELECT bids.*, bidder.username AS bidderName, @@ -204,48 +401,41 @@ export const insertBid = flopoDB.prepare(` VALUES (@id, @bidder_id, @market_offer_id, @offer_amount) `); -export const insertManyUsers = flopoDB.transaction(async (users) => { +/* ------------------------- + BULK TRANSACTIONS (synchronous) +----------------------------*/ +export const insertManyUsers = flopoDB.transaction((users) => { for (const user of users) try { - await insertUser.run(user); + insertUser.run(user); } catch (e) {} }); -export const updateManyUsers = flopoDB.transaction(async (users) => { + +export const updateManyUsers = flopoDB.transaction((users) => { for (const user of users) try { - await updateUser.run(user); + updateUser.run(user); } catch (e) { console.log(`[${Date.now()}] user update failed`); } }); -export const insertManySkins = flopoDB.transaction(async (skins) => { +export const insertManySkins = flopoDB.transaction((skins) => { for (const skin of skins) try { - await insertSkin.run(skin); + insertSkin.run(skin); } catch (e) {} }); -export const updateManySkins = flopoDB.transaction(async (skins) => { +export const updateManySkins = flopoDB.transaction((skins) => { for (const skin of skins) try { - await updateSkin.run(skin); + updateSkin.run(skin); } catch (e) {} }); -export const stmtLogs = flopoDB.prepare(` - CREATE TABLE IF NOT EXISTS logs - ( - id PRIMARY KEY, - user_id TEXT REFERENCES users, - action TEXT, - target_user_id TEXT REFERENCES users, - coins_amount INTEGER, - user_new_amount INTEGER, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP - ) -`); -stmtLogs.run(); - +/* ------------------------- + LOGS +----------------------------*/ export const insertLog = flopoDB.prepare( `INSERT INTO logs (id, user_id, action, target_user_id, coins_amount, user_new_amount) VALUES (@id, @user_id, @action, @target_user_id, @coins_amount, @user_new_amount)`, @@ -253,24 +443,9 @@ export const insertLog = flopoDB.prepare( export const getLogs = flopoDB.prepare("SELECT * FROM logs"); export const getUserLogs = flopoDB.prepare("SELECT * FROM logs WHERE user_id = @user_id"); -export const stmtGames = flopoDB.prepare(` - CREATE TABLE IF NOT EXISTS games - ( - id PRIMARY KEY, - p1 TEXT REFERENCES users, - p2 TEXT REFERENCES users, - p1_score INTEGER, - p2_score INTEGER, - p1_elo INTEGER, - p2_elo INTEGER, - p1_new_elo INTEGER, - p2_new_elo INTEGER, - type TEXT, - timestamp TIMESTAMP - ) -`); -stmtGames.run(); - +/* ------------------------- + GAMES +----------------------------*/ export const insertGame = flopoDB.prepare( `INSERT INTO games (id, p1, p2, p1_score, p2_score, p1_elo, p2_elo, p1_new_elo, p2_new_elo, type, timestamp) VALUES (@id, @p1, @p2, @p1_score, @p2_score, @p1_elo, @p2_elo, @p1_new_elo, @p2_new_elo, @type, @timestamp)`, @@ -280,15 +455,9 @@ export const getUserGames = flopoDB.prepare( "SELECT * FROM games WHERE p1 = @user_id OR p2 = @user_id ORDER BY timestamp", ); -export const stmtElos = flopoDB.prepare(` - CREATE TABLE IF NOT EXISTS elos - ( - id PRIMARY KEY REFERENCES users, - elo INTEGER - ) -`); -stmtElos.run(); - +/* ------------------------- + ELOS +----------------------------*/ export const insertElos = flopoDB.prepare(`INSERT INTO elos (id, elo) VALUES (@id, @elo)`); export const getElos = flopoDB.prepare(`SELECT * @@ -302,20 +471,9 @@ export const getUsersByElo = flopoDB.prepare( "SELECT * FROM users JOIN elos ON elos.id = users.id ORDER BY elos.elo DESC", ); -export const stmtSOTD = flopoDB.prepare(` - CREATE TABLE IF NOT EXISTS sotd - ( - id INT PRIMARY KEY, - tableauPiles TEXT, - foundationPiles TEXT, - stockPile TEXT, - wastePile TEXT, - isDone BOOLEAN DEFAULT false, - seed TEXT - ) -`); -stmtSOTD.run(); - +/* ------------------------- + SOTD +----------------------------*/ export const getSOTD = flopoDB.prepare(`SELECT * FROM sotd WHERE id = '0'`); @@ -326,18 +484,6 @@ export const deleteSOTD = flopoDB.prepare(`DELETE FROM sotd WHERE id = '0'`); -export const stmtSOTDStats = flopoDB.prepare(` - CREATE TABLE IF NOT EXISTS sotd_stats - ( - id TEXT PRIMARY KEY, - user_id TEXT REFERENCES users, - time INTEGER, - moves INTEGER, - score INTEGER - ) -`); -stmtSOTDStats.run(); - export const getAllSOTDStats = flopoDB.prepare(`SELECT sotd_stats.*, users.globalName FROM sotd_stats JOIN users ON users.id = sotd_stats.user_id @@ -353,6 +499,13 @@ export const deleteUserSOTDStats = flopoDB.prepare(`DELETE FROM sotd_stats WHERE user_id = ?`); +/* ------------------------- + Market queries already declared above (kept for completeness) +----------------------------*/ + +/* ------------------------- + pruneOldLogs +----------------------------*/ export async function pruneOldLogs() { const users = flopoDB .prepare( diff --git a/src/utils/ai.js b/src/utils/ai.js index f1a1296..17b02fe 100644 --- a/src/utils/ai.js +++ b/src/utils/ai.js @@ -34,7 +34,7 @@ export async function gork(messageHistory) { try { // --- OpenAI Provider --- - if (modelProvider === "OpenAI" && openai) { + if (modelProvider.toLowerCase() === "openai" && openai) { const completion = await openai.chat.completions.create({ model: "gpt-5", // Using a modern, cost-effective model reasoning_effort: "low", @@ -44,7 +44,7 @@ export async function gork(messageHistory) { } // --- Google Gemini Provider --- - else if (modelProvider === "Gemini" && gemini) { + else if (modelProvider.toLowerCase() === "gemini" && gemini) { // Gemini requires a slightly different history format. messageHistory.forEach((message) => { console.log(message.role); @@ -76,7 +76,7 @@ export async function gork(messageHistory) { } // --- Mistral Provider --- - else if (modelProvider === "Mistral" && mistral) { + else if (modelProvider.toLowerCase() === "mistral" && mistral) { const chatResponse = await mistral.chat({ model: "mistral-large-latest", messages: messageHistory, diff --git a/src/utils/index.js b/src/utils/index.js index f8f246e..429fa35 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -136,7 +136,7 @@ export function setupCronJobs(client, io) { }); // Daily at midnight: Reset daily rewards and init SOTD - cron.schedule("0 0 * * *", async () => { + cron.schedule(process.env.CRON_EXPR, async () => { console.log("[Cron] Running daily midnight tasks..."); try { resetDailyReward.run();