@@ -12,6 +12,8 @@ import (
1212 "sync"
1313 "time"
1414
15+ "github.com/polarhive/ash/util"
16+
1517 "maunium.net/go/mautrix"
1618 "maunium.net/go/mautrix/event"
1719 "maunium.net/go/mautrix/id"
@@ -166,7 +168,17 @@ func (s *KnockKnockState) Delete(evID id.EventID) {
166168// Yap leaderboard
167169// ---------------------------------------------------------------------------
168170
169- // QueryTopYappers returns the top N message senders in the last 24h for the
171+ // YapTimezone is the timezone used to determine "start of day" for the yap
172+ // leaderboard. Defaults to UTC. Set via config.json "TIMEZONE" field.
173+ var YapTimezone = time .UTC
174+
175+ // startOfToday returns midnight in the configured YapTimezone as Unix millis.
176+ func startOfToday () int64 {
177+ now := time .Now ().In (YapTimezone )
178+ return time .Date (now .Year (), now .Month (), now .Day (), 0 , 0 , 0 , 0 , YapTimezone ).UnixMilli ()
179+ }
180+
181+ // QueryTopYappers returns the top N message senders since midnight for the
170182// current room, excluding messages that start with the bot label (e.g. [BOT]).
171183func QueryTopYappers (ctx context.Context , db * sql.DB , matrixClient * mautrix.Client , ev * event.Event , args string , replyLabel string , mention bool ) (string , error ) {
172184 if db == nil {
@@ -190,7 +202,7 @@ func QueryTopYappers(ctx context.Context, db *sql.DB, matrixClient *mautrix.Clie
190202 }
191203
192204 roomID := string (ev .RoomID )
193- cutoff := time . Now (). Add ( - 24 * time . Hour ). UnixMilli ()
205+ cutoff := startOfToday ()
194206
195207 rows , err := db .QueryContext (ctx , `
196208 SELECT sender, SUM(LENGTH(body) - LENGTH(REPLACE(body, ' ', '')) + 1) as word_count
@@ -245,13 +257,13 @@ func QueryTopYappers(ctx context.Context, db *sql.DB, matrixClient *mautrix.Clie
245257 }
246258
247259 if len (entries ) == 0 {
248- return "no messages found in the last 24h " , nil
260+ return "no messages found today " , nil
249261 }
250262
251263 // Build plain text and HTML versions.
252264 var plain , html strings.Builder
253- plain .WriteString (replyLabel + "top yappers (last 24h ):\n " )
254- html .WriteString (replyLabel + "top yappers (last 24h ):<br>" )
265+ plain .WriteString (replyLabel + "top yappers (today ):\n " )
266+ html .WriteString (replyLabel + "top yappers (today ):<br>" )
255267 for i , e := range entries {
256268 plain .WriteString (fmt .Sprintf ("%d. %s \u2014 %d words\n " , i + 1 , e .display , e .count ))
257269 if mention {
@@ -281,7 +293,7 @@ func QueryTopYappers(ctx context.Context, db *sql.DB, matrixClient *mautrix.Clie
281293}
282294
283295// queryYapGuess handles "/bot yap guess N". It looks up the caller's actual
284- // position on the 24h word-count leaderboard and reports the difference.
296+ // position on today's (since midnight UTC) word-count leaderboard and reports the difference.
285297func queryYapGuess (ctx context.Context , db * sql.DB , matrixClient * mautrix.Client , ev * event.Event , guessArg string , replyLabel string ) (string , error ) {
286298 guess := 1
287299 if guessArg != "" {
@@ -292,7 +304,7 @@ func queryYapGuess(ctx context.Context, db *sql.DB, matrixClient *mautrix.Client
292304
293305 roomID := string (ev .RoomID )
294306 senderID := string (ev .Sender )
295- cutoff := time . Now (). Add ( - 24 * time . Hour ). UnixMilli ()
307+ cutoff := startOfToday ()
296308
297309 rows , err := db .QueryContext (ctx , `
298310 SELECT sender, SUM(LENGTH(body) - LENGTH(REPLACE(body, ' ', '')) + 1) as word_count
@@ -327,7 +339,7 @@ func queryYapGuess(ctx context.Context, db *sql.DB, matrixClient *mautrix.Client
327339 }
328340
329341 if actualPos == 0 {
330- return replyLabel + "you have no messages in the last 24h !" , nil
342+ return "you have no messages today !" , nil
331343 }
332344
333345 diff := guess - actualPos
@@ -360,6 +372,82 @@ func queryYapGuess(ctx context.Context, db *sql.DB, matrixClient *mautrix.Client
360372 return msg , nil
361373}
362374
375+ // ---------------------------------------------------------------------------
376+ // Random quote
377+ // ---------------------------------------------------------------------------
378+
379+ // QueryRandomQuote picks a random message from the room's history (excluding
380+ // bot messages and commands) and formats it as a quote.
381+ func QueryRandomQuote (ctx context.Context , db * sql.DB , matrixClient * mautrix.Client , ev * event.Event , args string , replyLabel string , mention bool ) (string , error ) {
382+ if db == nil {
383+ return "" , fmt .Errorf ("no database available" )
384+ }
385+
386+ roomID := string (ev .RoomID )
387+
388+ // Parse duration argument (default 24h)
389+ durSec , err := util .ParseDurationArg (args )
390+ if err != nil {
391+ durSec = 24 * 3600 // fallback to 24h
392+ }
393+ cutoff := time .Now ().Unix () - durSec
394+
395+ row := db .QueryRowContext (ctx , `
396+ SELECT sender, body, ts_ms
397+ FROM messages
398+ WHERE room_id = ?
399+ AND body NOT LIKE '[BOT]%'
400+ AND body NOT LIKE '/bot %'
401+ AND msgtype = 'm.text'
402+ AND LENGTH(body) > 5
403+ AND ts_ms >= ? * 1000
404+ ORDER BY RANDOM()
405+ LIMIT 1
406+ ` , roomID , cutoff )
407+
408+ var sender , body string
409+ var tsMs int64
410+ if err := row .Scan (& sender , & body , & tsMs ); err != nil {
411+ return "no messages found to quote" , nil
412+ }
413+
414+ // Resolve display name.
415+ display := sender
416+ if matrixClient != nil {
417+ if resp , err := matrixClient .JoinedMembers (ctx , ev .RoomID ); err == nil {
418+ if member , ok := resp .Joined [id .UserID (sender )]; ok && member .DisplayName != "" {
419+ display = member .DisplayName
420+ }
421+ }
422+ }
423+ if display == sender && strings .HasPrefix (sender , "@" ) {
424+ if idx := strings .Index (sender , ":" ); idx > 0 {
425+ display = sender [1 :idx ]
426+ }
427+ }
428+
429+ ts := time .UnixMilli (tsMs ).In (YapTimezone )
430+ date := ts .Format ("02 Jan 2006" )
431+
432+ plain := fmt .Sprintf ("%s> %s\n > \u2014 %s, %s" , replyLabel , body , display , date )
433+ html := fmt .Sprintf ("%s<blockquote>%s<br>\u2014 <i>%s, %s</i></blockquote>" , replyLabel , body , display , date )
434+
435+ if matrixClient != nil {
436+ content := event.MessageEventContent {
437+ MsgType : event .MsgText ,
438+ Body : plain ,
439+ Format : event .FormatHTML ,
440+ FormattedBody : html ,
441+ RelatesTo : & event.RelatesTo {InReplyTo : & event.InReplyTo {EventID : ev .ID }},
442+ }
443+ if _ , err := matrixClient .SendMessageEvent (ctx , ev .RoomID , event .EventMessage , & content ); err != nil {
444+ return "" , fmt .Errorf ("send quote reply: %w" , err )
445+ }
446+ return "" , nil
447+ }
448+ return plain , nil
449+ }
450+
363451// ---------------------------------------------------------------------------
364452// UwUify
365453// ---------------------------------------------------------------------------
0 commit comments