Add markov, emojiboard, achievements, miniflux plugins and adventure catch-up fix

New plugins: encrypted Markov chain generation (9 commands), emojiboard
reaction stats (5 views), Miniflux RSS feed integration (8 commands),
and 48 new achievements across 9 categories. Adventure startup now
unconditionally resets daily actions and respawns expired deaths to
handle missed resets from SQLite contention. README updated with all
new features.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
prosolis
2026-03-27 11:55:47 -07:00
parent 1b423b1b16
commit 81e6cecad9
9 changed files with 2219 additions and 136 deletions

View File

@@ -449,6 +449,473 @@ func (p *AchievementsPlugin) buildAchievements() []achievementDef {
return err == nil && month > 0 && tz != ""
},
},
// ── Economy ─────────────────────────────────────────────────────────
{
ID: "euro_first", Name: "First Paycheck", Description: "The economy has claimed another victim.",
Emoji: "💶",
Check: func(d *sql.DB, u id.UserID) bool {
var count int
err := d.QueryRow(
`SELECT COUNT(*) FROM euro_transactions WHERE user_id = ? AND amount > 0`,
string(u),
).Scan(&count)
return err == nil && count >= 1
},
},
{
ID: "euro_1k", Name: "Four Figures", Description: "You are now, technically, a thousandaire.",
Emoji: "💰",
Check: func(d *sql.DB, u id.UserID) bool {
var balance float64
err := d.QueryRow(
`SELECT balance FROM euro_balances WHERE user_id = ?`,
string(u),
).Scan(&balance)
return err == nil && balance >= 1000
},
},
{
ID: "euro_10k", Name: "High Roller", Description: "Someone stop this person.",
Emoji: "🤑",
Check: func(d *sql.DB, u id.UserID) bool {
var balance float64
err := d.QueryRow(
`SELECT balance FROM euro_balances WHERE user_id = ?`,
string(u),
).Scan(&balance)
return err == nil && balance >= 10000
},
},
{
ID: "euro_broke", Name: "Overdrafted", Description: "Character-building experience.",
Emoji: "📉",
Check: func(d *sql.DB, u id.UserID) bool {
var balance float64
err := d.QueryRow(
`SELECT balance FROM euro_balances WHERE user_id = ?`,
string(u),
).Scan(&balance)
return err == nil && balance < 0
},
},
{
ID: "euro_comeback", Name: "Comeback Kid", Description: "Against all odds.",
Emoji: "🔄",
Check: func(d *sql.DB, u id.UserID) bool {
// Granted by euro plugin when recovering from debt
return false
},
},
{
ID: "euro_generous", Name: "Philanthropist", Description: "Money is just a social construct anyway.",
Emoji: "🎁",
Check: func(d *sql.DB, u id.UserID) bool {
var total float64
err := d.QueryRow(
`SELECT COALESCE(SUM(ABS(amount)), 0) FROM euro_transactions WHERE user_id = ? AND reason = 'transfer' AND amount < 0`,
string(u),
).Scan(&total)
return err == nil && total >= 1000
},
},
// ── Blackjack ───────────────────────────────────────────────────────
{
ID: "bj_first_win", Name: "Natural", Description: "The table fears you. Slightly.",
Emoji: "🃏",
Check: func(d *sql.DB, u id.UserID) bool {
var won int
err := d.QueryRow(
`SELECT games_won FROM blackjack_scores WHERE user_id = ?`,
string(u),
).Scan(&won)
return err == nil && won >= 1
},
},
{
ID: "bj_blackjack", Name: "Blackjack", Description: "21. As the prophecy foretold.",
Emoji: "🎰",
Check: func(d *sql.DB, u id.UserID) bool {
// Granted by blackjack plugin on natural 21
return false
},
},
{
ID: "bj_bust", Name: "Going for It", Description: "Hope is a beautiful thing.",
Emoji: "💥",
Check: func(d *sql.DB, u id.UserID) bool {
var played, won int
err := d.QueryRow(
`SELECT games_played, games_won FROM blackjack_scores WHERE user_id = ?`,
string(u),
).Scan(&played, &won)
// Busts are approximated as losses (played - won)
return err == nil && (played-won) >= 10
},
},
{
ID: "bj_beat_twinbee", Name: "Gotcha 🐝", Description: "She is furious and will not forget this.",
Emoji: "🐝",
Check: func(d *sql.DB, u id.UserID) bool {
// Granted by blackjack plugin when beating the bot
return false
},
},
{
ID: "bj_100_hands", Name: "Card Shark", Description: "Statistically, you should have stopped.",
Emoji: "🦈",
Check: func(d *sql.DB, u id.UserID) bool {
var played int
err := d.QueryRow(
`SELECT games_played FROM blackjack_scores WHERE user_id = ?`,
string(u),
).Scan(&played)
return err == nil && played >= 100
},
},
// ── UNO ─────────────────────────────────────────────────────────────
{
ID: "uno_first_win", Name: "UNO!", Description: "One card. Victory. Glory.",
Emoji: "🟥",
Check: func(d *sql.DB, u id.UserID) bool {
// Granted by UNO plugin on first win
return false
},
},
{
ID: "uno_nomercy_win", Name: "No Mercy", Description: "You are a monster and we respect it.",
Emoji: "😈",
Check: func(d *sql.DB, u id.UserID) bool {
// Granted by UNO plugin on No Mercy mode win
return false
},
},
{
ID: "uno_draw_stack", Name: "Stack Overflow", Description: "The pile grows. Your opponents weep.",
Emoji: "📚",
Check: func(d *sql.DB, u id.UserID) bool {
// Granted by UNO plugin on 3+ draw stacks in one game
return false
},
},
{
ID: "uno_comeback", Name: "Down to Zero", Description: "The math said no. You said yes.",
Emoji: "🔥",
Check: func(d *sql.DB, u id.UserID) bool {
// Granted by UNO plugin on winning from 10+ cards
return false
},
},
// ── Texas Hold'em ───────────────────────────────────────────────────
{
ID: "holdem_first_win", Name: "Ante Up", Description: "A pot has been claimed.",
Emoji: "♠️",
Check: func(d *sql.DB, u id.UserID) bool {
var won int
err := d.QueryRow(
`SELECT total_won FROM holdem_scores WHERE user_id = ?`,
string(u),
).Scan(&won)
return err == nil && won > 0
},
},
{
ID: "holdem_royal_flush", Name: "Royal Flush", Description: "This has never happened before in recorded history.",
Emoji: "👑",
Check: func(d *sql.DB, u id.UserID) bool {
// Granted by holdem plugin on royal flush
return false
},
},
{
ID: "holdem_bluff", Name: "Poker Face", Description: "The audacity. Incredible.",
Emoji: "😶",
Check: func(d *sql.DB, u id.UserID) bool {
// Granted by holdem plugin on high-card-only win
return false
},
},
{
ID: "holdem_beat_npc", Name: "Outsmarted the Bot", Description: "CFR trained on millions of hands. You trained on vibes.",
Emoji: "🤖",
Check: func(d *sql.DB, u id.UserID) bool {
// Granted by holdem plugin on beating CFR NPC
return false
},
},
{
ID: "holdem_allin_win", Name: "All In", Description: "Everything on the line. Everything gained.",
Emoji: "💎",
Check: func(d *sql.DB, u id.UserID) bool {
// Granted by holdem plugin on all-in win
return false
},
},
{
ID: "holdem_bounty", Name: "Bounty Hunter", Description: "They had families.",
Emoji: "🎯",
Check: func(d *sql.DB, u id.UserID) bool {
// Granted by holdem plugin on knocking out a player
return false
},
},
// ── Hangman ─────────────────────────────────────────────────────────
{
ID: "hangman_solve", Name: "Lexicographer", Description: "Language. You understand it.",
Emoji: "📖",
Check: func(d *sql.DB, u id.UserID) bool {
var won int
err := d.QueryRow(
`SELECT games_won FROM hangman_scores WHERE user_id = ?`,
string(u),
).Scan(&won)
return err == nil && won >= 1
},
},
{
ID: "hangman_first_guess", Name: "Mind Reader", Description: "How.",
Emoji: "🔮",
Check: func(d *sql.DB, u id.UserID) bool {
// Granted by hangman plugin on first-guess solve
return false
},
},
{
ID: "hangman_submitted", Name: "Contributor", Description: "Your words. Their suffering.",
Emoji: "✏️",
Check: func(d *sql.DB, u id.UserID) bool {
// Granted by hangman plugin when a submitted phrase is used
return false
},
},
{
ID: "hangman_executioner", Name: "Executioner", Description: "You did this.",
Emoji: "⚰️",
Check: func(d *sql.DB, u id.UserID) bool {
// Granted by hangman plugin on final wrong letter
return false
},
},
// ── Wordle ──────────────────────────────────────────────────────────
{
ID: "wordle_solve", Name: "Wordled", Description: "The letters obeyed you.",
Emoji: "🟩",
Check: func(d *sql.DB, u id.UserID) bool {
var solved int
err := d.QueryRow(
`SELECT puzzles_solved FROM wordle_stats WHERE user_id = ?`,
string(u),
).Scan(&solved)
return err == nil && solved >= 1
},
},
{
ID: "wordle_first_guess", Name: "Omniscient", Description: "That was statistically impossible.",
Emoji: "🧿",
Check: func(d *sql.DB, u id.UserID) bool {
// Granted by wordle plugin on 1-guess community solve
return false
},
},
{
ID: "wordle_streak_3", Name: "Hot Streak", Description: "Momentum.",
Emoji: "🔥",
Check: func(d *sql.DB, u id.UserID) bool {
// Granted by wordle plugin on 3 consecutive community wins
return false
},
},
{
ID: "wordle_bonus", Name: "Nerd", Description: "One of us.",
Emoji: "🎮",
Check: func(d *sql.DB, u id.UserID) bool {
// Granted by wordle plugin on bonus word solve
return false
},
},
{
ID: "wordle_closer", Name: "Closer", Description: "The community thanks you. Sort of.",
Emoji: "🏁",
Check: func(d *sql.DB, u id.UserID) bool {
// Granted by wordle plugin on submitting the winning guess
return false
},
},
// ── Adventure ───────────────────────────────────────────────────────
{
ID: "adv_first", Name: "Adventurer", Description: "The dungeon awaits. Probably.",
Emoji: "⚔️",
Check: func(d *sql.DB, u id.UserID) bool {
var count int
err := d.QueryRow(
`SELECT COUNT(*) FROM adventure_activity_log WHERE user_id = ?`,
string(u),
).Scan(&count)
return err == nil && count >= 1
},
},
{
ID: "adv_died", Name: "Respawning", Description: "This is fine.",
Emoji: "💀",
Check: func(d *sql.DB, u id.UserID) bool {
// Granted by adventure plugin on death
return false
},
},
{
ID: "adv_revived", Name: "Second Life", Description: "Someone cared enough.",
Emoji: "✨",
Check: func(d *sql.DB, u id.UserID) bool {
// Granted by adventure plugin on admin revive
return false
},
},
{
ID: "adv_streak_7", Name: "Daily Grind", Description: "Committed. Concerningly so.",
Emoji: "📅",
Check: func(d *sql.DB, u id.UserID) bool {
var streak int
err := d.QueryRow(
`SELECT best_streak FROM adventure_characters WHERE user_id = ?`,
string(u),
).Scan(&streak)
return err == nil && streak >= 7
},
},
{
ID: "adv_streak_30", Name: "Devoted", Description: "Please go outside.",
Emoji: "🗓️",
Check: func(d *sql.DB, u id.UserID) bool {
var streak int
err := d.QueryRow(
`SELECT best_streak FROM adventure_characters WHERE user_id = ?`,
string(u),
).Scan(&streak)
return err == nil && streak >= 30
},
},
{
ID: "adv_max_level", Name: "Legendary", Description: "What's next? Nothing. You've done it.",
Emoji: "🏆",
Check: func(d *sql.DB, u id.UserID) bool {
// Granted by adventure plugin on reaching max level
return false
},
},
{
ID: "adv_treasure_cap", Name: "Hoarder", Description: "No more pockets.",
Emoji: "💎",
Check: func(d *sql.DB, u id.UserID) bool {
var count int
err := d.QueryRow(
`SELECT COUNT(*) FROM adventure_treasures WHERE user_id = ?`,
string(u),
).Scan(&count)
return err == nil && count >= 3
},
},
{
ID: "adv_grudge_win", Name: "Revenge", Description: "Redemption arc complete.",
Emoji: "⚡",
Check: func(d *sql.DB, u id.UserID) bool {
// Granted by adventure plugin on grudge location success
return false
},
},
{
ID: "adv_party", Name: "Party of Two", Description: "Accidental cooperation.",
Emoji: "🤝",
Check: func(d *sql.DB, u id.UserID) bool {
// Granted by adventure plugin on party bonus
return false
},
},
{
ID: "adv_twinbee_gift", Name: "Blessed by TwinBee", Description: "The bee has chosen you. Unknown why.",
Emoji: "🐝",
Check: func(d *sql.DB, u id.UserID) bool {
// Granted by adventure plugin on TwinBee NPC buff
return false
},
},
// ── Word of the Day ─────────────────────────────────────────────────
{
ID: "wotd_first", Name: "Logophile Jr.", Description: "25 XP well earned.",
Emoji: "📝",
Check: func(d *sql.DB, u id.UserID) bool {
var count int
err := d.QueryRow(
`SELECT COUNT(DISTINCT date) FROM wotd_usage WHERE user_id = ? AND count > 0`,
string(u),
).Scan(&count)
return err == nil && count >= 1
},
},
{
ID: "wotd_week", Name: "Vocabulary Builder", Description: "The dictionary approves.",
Emoji: "📚",
Check: func(d *sql.DB, u id.UserID) bool {
var count int
err := d.QueryRow(
`SELECT COUNT(DISTINCT date) FROM wotd_usage WHERE user_id = ? AND count > 0`,
string(u),
).Scan(&count)
return err == nil && count >= 7
},
},
{
ID: "wotd_30", Name: "Wordsmith Elite", Description: "You have transcended mere communication.",
Emoji: "🎓",
Check: func(d *sql.DB, u id.UserID) bool {
var count int
err := d.QueryRow(
`SELECT COUNT(DISTINCT date) FROM wotd_usage WHERE user_id = ? AND count > 0`,
string(u),
).Scan(&count)
return err == nil && count >= 30
},
},
// ── Community & Social ──────────────────────────────────────────────
{
ID: "quote_saved", Name: "Quotable", Description: "Posterity has noted your words.",
Emoji: "💬",
Check: func(d *sql.DB, u id.UserID) bool {
// Granted by quote plugin when a quote is saved for the user
return false
},
},
{
ID: "quote_saved_10", Name: "Memorable", Description: "You say things worth remembering. Allegedly.",
Emoji: "🏅",
Check: func(d *sql.DB, u id.UserID) bool {
// Granted by quote plugin when 10 quotes are saved for the user
return false
},
},
{
ID: "missing_poster", Name: "Milk Carton", Description: "Someone noticed you were gone. Eventually.",
Emoji: "🥛",
Check: func(d *sql.DB, u id.UserID) bool {
// Granted by missing poster plugin
return false
},
},
{
ID: "tarot_spread", Name: "The Fool's Journey", Description: "Past, present, future. All vibes.",
Emoji: "🔮",
Check: func(d *sql.DB, u id.UserID) bool {
// Granted by tarot plugin on three-card spread
return false
},
},
}
}