Add horoscope, !time @user, profile achievements; fix mention detection and multiple bugs

- Add !horoscope command and daily horoscope digest using Free Horoscope API
- Enhance !time to support @user timezone lookups
- Add profile completeness achievements (birthday, timezone, both set)
- Fix Matrix mention detection in reputation and LLM passive plugins
- Fix !whois reputation query (reason string mismatch and calculation)
- Fix !time IANA case-sensitivity and username/city collision
- Fix timezone default to empty string for achievement detection
- Scope all leaderboards to room members (XP, rep, potty, insult, first, rankings, emoji)
- Fix XP rank calculation to handle tied scores correctly
- Replace skull emoji with bomb for bot insult reactions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
prosolis
2026-03-09 23:55:02 -07:00
parent 2c191ec464
commit 03def6463f
24 changed files with 5057 additions and 116 deletions

View File

@@ -151,9 +151,9 @@ func (p *MilkCartonPlugin) handleHaveYouSeenThem(ctx MessageContext) error {
return p.SendReply(ctx.RoomID, ctx.EventID, "Usage: !haveyouseenthem @user")
}
targetID := id.UserID(strings.TrimPrefix(strings.TrimSpace(args), "@"))
if !strings.Contains(string(targetID), ":") {
return p.SendReply(ctx.RoomID, ctx.EventID, "Please provide a full Matrix user ID (e.g. @user:server.com)")
targetID, ok := p.ResolveUser(args)
if !ok {
return p.SendReply(ctx.RoomID, ctx.EventID, "Could not find a user matching that name.")
}
// Rate limit: 1 carton per room per day
@@ -414,7 +414,7 @@ func (p *MilkCartonPlugin) renderCarton(
dc.DrawImage(avatarResized, int(photoX), int(photoY))
} else {
// Draw silhouette
p.drawSilhouette(dc, photoX, photoY, float64(photoSize))
drawSilhouette(dc, photoX, photoY, float64(photoSize))
}
// Name
@@ -503,22 +503,6 @@ func (p *MilkCartonPlugin) renderCarton(
return buf.Bytes(), nil
}
func (p *MilkCartonPlugin) drawSilhouette(dc *gg.Context, x, y, size float64) {
cx := x + size/2
cy := y + size/2
dc.SetColor(color.RGBA{170, 165, 155, 255})
// Head
headRadius := size * 0.18
dc.DrawCircle(cx, cy-size*0.12, headRadius)
dc.Fill()
// Body
dc.DrawEllipse(cx, cy+size*0.22, size*0.28, size*0.25)
dc.Fill()
}
// deriveCharacteristics generates flavor text from user stats.
func (p *MilkCartonPlugin) deriveCharacteristics(userID string) []string {
d := db.Get()
@@ -613,11 +597,11 @@ func resizeImage(src image.Image, targetW, targetH int) image.Image {
dc := gg.NewContext(targetW, targetH)
dc.DrawImage(src, (targetW-newW)/2, (targetH-newH)/2)
// Use gg's built-in scaling
// Use gg's built-in scaling — anchor to top so heads aren't cropped
dcScaled := gg.NewContext(targetW, targetH)
dcScaled.Scale(scale, scale)
offsetX := -float64(srcW)/2 + float64(targetW)/(2*scale)
offsetY := -float64(srcH)/2 + float64(targetH)/(2*scale)
offsetY := 0.0 // top-anchored crop
dcScaled.DrawImage(src, int(offsetX), int(offsetY))
return dcScaled.Image()