169 lines
4.7 KiB
Go
169 lines
4.7 KiB
Go
package domain
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
// MaxChannelStatsDays bounds every aggregate query independently of the
|
|
// client request. The RPC currently requests seven days, while keeping a
|
|
// small domain ceiling makes future callers safe by construction.
|
|
MaxChannelStatsDays = 31
|
|
// MaxChannelStatsTopPosters bounds the user hydration work at the RPC edge.
|
|
MaxChannelStatsTopPosters = 10
|
|
// MaxChannelStatsRecentPosts bounds correlated interaction aggregation.
|
|
MaxChannelStatsRecentPosts = 10
|
|
// MaxChannelMessagePublicForwards is Telegram's public-forwards page cap.
|
|
MaxChannelMessagePublicForwards = 100
|
|
)
|
|
|
|
var ErrStatsOffsetInvalid = errors.New("stats offset invalid")
|
|
|
|
// StatsPeriod is a half-open Unix-second range [MinDate, MaxDate). Previous
|
|
// values always use the equally sized range immediately before MinDate.
|
|
type StatsPeriod struct {
|
|
MinDate int
|
|
MaxDate int
|
|
}
|
|
|
|
func (p StatsPeriod) Valid() bool {
|
|
return p.MinDate > 0 && p.MaxDate > p.MinDate &&
|
|
p.MaxDate-p.MinDate <= MaxChannelStatsDays*86400
|
|
}
|
|
|
|
func (p StatsPeriod) PreviousMinDate() int {
|
|
return p.MinDate - (p.MaxDate - p.MinDate)
|
|
}
|
|
|
|
// StatsValueAndPrev is one current-period value and its previous-period peer.
|
|
type StatsValueAndPrev struct {
|
|
Current float64
|
|
Previous float64
|
|
}
|
|
|
|
// StatsReactionCount is one protocol-neutral reaction series value.
|
|
type StatsReactionCount struct {
|
|
Reaction MessageReaction
|
|
Count int
|
|
}
|
|
|
|
// ChannelStatsDay is a UTC-day bucket. Date is the bucket start.
|
|
type ChannelStatsDay struct {
|
|
Date int
|
|
Members int
|
|
NewMembers int
|
|
Messages int
|
|
Viewers int
|
|
Posters int
|
|
Views int
|
|
Shares int
|
|
Reactions int
|
|
ByReaction []StatsReactionCount
|
|
}
|
|
|
|
type ChannelStatsTopPoster struct {
|
|
UserID int64
|
|
Messages int
|
|
AvgChars int
|
|
}
|
|
|
|
type ChannelStatsRecentPost struct {
|
|
MessageID int
|
|
Views int
|
|
Forwards int
|
|
Reactions int
|
|
}
|
|
|
|
// ChannelStats is the durable minimum needed by broadcast/megagroup stats.
|
|
// Unsupported Telegram dimensions (language, notification mute, IV sources)
|
|
// are deliberately absent so the RPC edge can return statsGraphError instead
|
|
// of manufacturing zero-valued facts.
|
|
type ChannelStats struct {
|
|
Channel Channel
|
|
Period StatsPeriod
|
|
Members StatsValueAndPrev
|
|
Messages StatsValueAndPrev
|
|
Viewers StatsValueAndPrev
|
|
Posters StatsValueAndPrev
|
|
ViewsPerPost StatsValueAndPrev
|
|
SharesPerPost StatsValueAndPrev
|
|
ReactionsPerPost StatsValueAndPrev
|
|
Days []ChannelStatsDay
|
|
TopPosters []ChannelStatsTopPoster
|
|
RecentPosts []ChannelStatsRecentPost
|
|
}
|
|
|
|
type ChannelStatsRequest struct {
|
|
ViewerUserID int64
|
|
ChannelID int64
|
|
Period StatsPeriod
|
|
}
|
|
|
|
// ChannelMessageStats contains event-time view and reaction buckets for one
|
|
// existing channel message.
|
|
type ChannelMessageStats struct {
|
|
Channel Channel
|
|
Message ChannelMessage
|
|
Period StatsPeriod
|
|
Days []ChannelStatsDay
|
|
}
|
|
|
|
type ChannelMessageStatsRequest struct {
|
|
ViewerUserID int64
|
|
ChannelID int64
|
|
MessageID int
|
|
Period StatsPeriod
|
|
}
|
|
|
|
// ChannelMessagePublicForwardListRequest pages public channel/supergroup
|
|
// messages whose forward header identifies one exact source channel post.
|
|
type ChannelMessagePublicForwardListRequest struct {
|
|
ViewerUserID int64
|
|
ChannelID int64
|
|
MessageID int
|
|
Offset string
|
|
Limit int
|
|
}
|
|
|
|
type ChannelMessagePublicForwardList struct {
|
|
Count int
|
|
Messages []ChannelMessage
|
|
NextOffset string
|
|
}
|
|
|
|
// ChannelMessagePublicForwardCursor is ordered by date DESC, destination
|
|
// channel ASC, message id DESC. A versioned textual form is intentionally
|
|
// opaque to clients while staying easy to validate and log.
|
|
type ChannelMessagePublicForwardCursor struct {
|
|
Date int
|
|
ChannelID int64
|
|
MessageID int
|
|
}
|
|
|
|
func ParseChannelMessagePublicForwardCursor(offset string) (ChannelMessagePublicForwardCursor, error) {
|
|
if offset == "" {
|
|
return ChannelMessagePublicForwardCursor{}, nil
|
|
}
|
|
parts := strings.Split(offset, ":")
|
|
if len(parts) != 4 || parts[0] != "cmf1" {
|
|
return ChannelMessagePublicForwardCursor{}, ErrStatsOffsetInvalid
|
|
}
|
|
date, err1 := strconv.Atoi(parts[1])
|
|
channelID, err2 := strconv.ParseInt(parts[2], 10, 64)
|
|
messageID, err3 := strconv.Atoi(parts[3])
|
|
if err1 != nil || err2 != nil || err3 != nil || date <= 0 || channelID <= 0 ||
|
|
messageID <= 0 || messageID > MaxMessageBoxID {
|
|
return ChannelMessagePublicForwardCursor{}, ErrStatsOffsetInvalid
|
|
}
|
|
return ChannelMessagePublicForwardCursor{Date: date, ChannelID: channelID, MessageID: messageID}, nil
|
|
}
|
|
|
|
func FormatChannelMessagePublicForwardCursor(message ChannelMessage) string {
|
|
if message.Date <= 0 || message.ChannelID <= 0 || message.ID <= 0 {
|
|
return ""
|
|
}
|
|
return fmt.Sprintf("cmf1:%d:%d:%d", message.Date, message.ChannelID, message.ID)
|
|
}
|