Add market plugin, arena, holidays, UNO stacking fix, and multiple UX improvements

- Market plugin (#49): daily index snapshots (Yahoo Finance + Finnhub fallback),
  Ollama-generated summaries, !howsthemarket, !marketstatus, !marketreport commands,
  exchange hours with DST support, 30-min room cooldown
- Adventure arena: 5-tier combat gauntlet with 20 monsters, risk-reward cashout,
  death lockout changed from 24h to midnight UTC across all code and flavor text
- Adventure holidays: double daily actions on holidays
- Adventure DM fix: 15-minute response window prevents bare numbers from triggering
  adventure during UNO games
- Adventure scheduler: jitter between morning DMs to avoid Matrix rate limits
- Adventure revive: wire up adv_revived achievement on admin revive
- Column migration system for existing databases (holiday_action_taken)
- Fix italic markdown rendering after newlines
- Fix holdem DMs broken by space groups including DM rooms
- UNO No Mercy: remove stacking escalation rule (any draw card stacks on any other)
- UNO multiplayer: add turn announcements after stack absorption and turn passes,
  jitter between rapid-fire messages with safe mutex handling
- Birthday: add €1,000 gift and 10 XP + €100 community celebration bonus
- Market: guard against division by zero, nil pointer, and timezone errors
- README updates: plugin count 48→49, all new commands, feature flags, architecture

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
prosolis
2026-03-27 21:26:19 -07:00
parent 81e6cecad9
commit 0d3485c7c2
22 changed files with 4327 additions and 116 deletions

22
main.go
View File

@@ -117,7 +117,8 @@ func main() {
registry.Register(plugin.NewBlackjackPlugin(client, euroPlugin))
registry.Register(plugin.NewUnoPlugin(client, euroPlugin))
registry.Register(plugin.NewHoldemPlugin(client, euroPlugin))
registry.Register(plugin.NewAdventurePlugin(client, euroPlugin))
adventurePlugin := plugin.NewAdventurePlugin(client, euroPlugin)
registry.Register(adventurePlugin)
wordlePlugin := plugin.NewWordlePlugin(client, euroPlugin)
registry.Register(wordlePlugin)
@@ -127,7 +128,9 @@ func main() {
registry.Register(plugin.NewTarotPlugin(client, ratePlugin))
// Tracking (passive)
registry.Register(plugin.NewAchievementsPlugin(client, registry))
achievementsPlugin := plugin.NewAchievementsPlugin(client, registry)
registry.Register(achievementsPlugin)
adventurePlugin.SetAchievements(achievementsPlugin)
registry.Register(plugin.NewReactionsPlugin(client))
registry.Register(plugin.NewMarkovPlugin(client))
registry.Register(plugin.NewURLsPlugin(client))
@@ -147,7 +150,7 @@ func main() {
registry.Register(holidaysPlugin)
gamingPlugin := plugin.NewGamingPlugin(client)
registry.Register(gamingPlugin)
birthdayPlugin := plugin.NewBirthdayPlugin(client, xpPlugin)
birthdayPlugin := plugin.NewBirthdayPlugin(client, xpPlugin, euroPlugin)
registry.Register(birthdayPlugin)
// Satirical
@@ -158,6 +161,10 @@ func main() {
horoscopePlugin := plugin.NewHoroscopePlugin(client)
registry.Register(horoscopePlugin)
// Finance — Market overview
marketPlugin := plugin.NewMarketPlugin(client)
registry.Register(marketPlugin)
// Utility / Meta
registry.Register(plugin.NewBotInfoPlugin(client))
registry.Register(plugin.NewHowAmIPlugin(client))
@@ -285,7 +292,7 @@ func main() {
// ---- Set up cron scheduler ----
scheduler := cron.New(cron.WithChain(cron.Recover(cronLogger{})))
setupScheduledJobs(scheduler, client, wotdPlugin, holidaysPlugin, gamingPlugin, birthdayPlugin, animePlugin, moviesPlugin, concertsPlugin, esteemedPlugin, forexPlugin, minifluxPlugin)
setupScheduledJobs(scheduler, client, wotdPlugin, holidaysPlugin, gamingPlugin, birthdayPlugin, animePlugin, moviesPlugin, concertsPlugin, esteemedPlugin, forexPlugin, minifluxPlugin, marketPlugin)
scheduler.Start()
// ---- Start syncing ----
@@ -338,6 +345,7 @@ func setupScheduledJobs(
esteemed *plugin.EsteemPlugin,
forex *plugin.ForexPlugin,
miniflux *plugin.MinifluxPlugin,
market *plugin.MarketPlugin,
) {
rooms := getRooms()
@@ -421,6 +429,12 @@ func setupScheduledJobs(
forex.DailyPoll()
})
// Market data daily pull at 23:00 UTC (after all target markets close)
c.AddFunc("0 23 * * *", func() {
slog.Info("scheduler: market daily pull")
market.DailyPull()
})
// Space groups refresh every hour
c.AddFunc("0 * * * *", func() {
slog.Info("scheduler: refreshing space groups")