Ctrl K
ComunidadGuías…

internal/domain/stats.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
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)
}