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

View File

@@ -206,7 +206,7 @@ func (sg *SpaceGroupManager) Refresh() {
return
}
// Fetch members for each room
// Fetch members for each room, skipping DM rooms (≤2 members)
roomMembers := make(map[id.RoomID]map[id.UserID]bool, len(rooms))
for _, roomID := range rooms {
resp, err := sg.client.JoinedMembers(ctx, roomID)
@@ -214,6 +214,9 @@ func (sg *SpaceGroupManager) Refresh() {
slog.Warn("space_groups: failed to get members", "room", roomID, "err", err)
continue
}
if len(resp.Joined) <= 2 {
continue // skip DM rooms
}
members := make(map[id.UserID]bool, len(resp.Joined))
for uid := range resp.Joined {
members[uid] = true
@@ -472,9 +475,9 @@ func (b *Base) ResolveUser(input string, roomIDs ...id.RoomID) (id.UserID, bool)
// (**bold**, _italic_, `code`, newlines) to Matrix-compatible HTML.
var (
mdBoldRe = regexp.MustCompile(`\*\*(.+?)\*\*`)
mdItalicRe = regexp.MustCompile(`(?:^|[ (])_([^_]+?)_(?:$|[ ).,!?])`)
mdItalicRe = regexp.MustCompile(`(?:^|[\n (])_([^_]+?)_(?:$|[\n ).,!?])`)
mdCodeRe = regexp.MustCompile("`([^`]+)`")
mdHasFmt = regexp.MustCompile(`\*\*|(?:^|[ (])_[^_]+_(?:$|[ ).,!?])|` + "`")
mdHasFmt = regexp.MustCompile(`\*\*|(?:^|[\n (])_[^_]+_(?:$|[\n ).,!?])|` + "`")
)
func simpleMarkdownToHTML(text string) string {