mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-02-02 13:02:56 +00:00
[feature] Add instance-stats-randomize
config option (#3718)
* [feature] Add `instance-stats-randomize` config option * don't use cache (overkill)
This commit is contained in:
parent
c47b9bd1d1
commit
a55bd6d2bd
|
@ -138,4 +138,15 @@ instance-subscriptions-process-from: "23:00"
|
|||
# Examples: ["24h", "72h", "12h"]
|
||||
# Default: "24h" (once per day).
|
||||
instance-subscriptions-process-every: "24h"
|
||||
|
||||
# Bool. Set this to true to randomize stats served at
|
||||
# the /api/v1|v2/instance and /nodeinfo/2.0 endpoints.
|
||||
#
|
||||
# This can be useful when you don't want bots to obtain
|
||||
# reliable information about the amount of users and
|
||||
# statuses on your instance.
|
||||
#
|
||||
# Options: [true, false]
|
||||
# Default: false
|
||||
instance-stats-randomize: false
|
||||
```
|
||||
|
|
|
@ -425,6 +425,17 @@ instance-subscriptions-process-from: "23:00"
|
|||
# Default: "24h" (once per day).
|
||||
instance-subscriptions-process-every: "24h"
|
||||
|
||||
# Bool. Set this to true to randomize stats served at
|
||||
# the /api/v1|v2/instance and /nodeinfo/2.0 endpoints.
|
||||
#
|
||||
# This can be useful when you don't want bots to obtain
|
||||
# reliable information about the amount of users and
|
||||
# statuses on your instance.
|
||||
#
|
||||
# Options: [true, false]
|
||||
# Default: false
|
||||
instance-stats-randomize: false
|
||||
|
||||
###########################
|
||||
##### ACCOUNTS CONFIG #####
|
||||
###########################
|
||||
|
|
|
@ -21,7 +21,9 @@
|
|||
"net/http"
|
||||
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
@ -58,6 +60,12 @@ func (m *Module) InstanceInformationGETHandlerV1(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
if config.GetInstanceStatsRandomize() {
|
||||
// Replace actual stats with cached randomized ones.
|
||||
instance.Stats["user_count"] = util.Ptr(int(instance.RandomStats.TotalUsers))
|
||||
instance.Stats["status_count"] = util.Ptr(int(instance.RandomStats.Statuses))
|
||||
}
|
||||
|
||||
apiutil.JSON(c, http.StatusOK, instance)
|
||||
}
|
||||
|
||||
|
@ -93,5 +101,10 @@ func (m *Module) InstanceInformationGETHandlerV2(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
if config.GetInstanceStatsRandomize() {
|
||||
// Replace actual stats with cached randomized ones.
|
||||
instance.Usage.Users.ActiveMonth = int(instance.RandomStats.MonthlyActiveUsers)
|
||||
}
|
||||
|
||||
apiutil.JSON(c, http.StatusOK, instance)
|
||||
}
|
||||
|
|
|
@ -17,7 +17,10 @@
|
|||
|
||||
package model
|
||||
|
||||
import "mime/multipart"
|
||||
import (
|
||||
"mime/multipart"
|
||||
"time"
|
||||
)
|
||||
|
||||
// InstanceSettingsUpdateRequest models an instance update request.
|
||||
//
|
||||
|
@ -148,3 +151,11 @@ type InstanceConfigurationEmojis struct {
|
|||
// example: 51200
|
||||
EmojiSizeLimit int `json:"emoji_size_limit"`
|
||||
}
|
||||
|
||||
// swagger:ignore
|
||||
type RandomStats struct {
|
||||
Statuses int64
|
||||
TotalUsers int64
|
||||
MonthlyActiveUsers int64
|
||||
Generated time.Time
|
||||
}
|
||||
|
|
|
@ -110,6 +110,13 @@ type InstanceV1 struct {
|
|||
Terms string `json:"terms,omitempty"`
|
||||
// Raw (unparsed) version of terms.
|
||||
TermsRaw string `json:"terms_text,omitempty"`
|
||||
|
||||
// Random stats generated for the instance.
|
||||
// Only used if `instance-stats-randomize` is true.
|
||||
// Not serialized to the frontend.
|
||||
//
|
||||
// swagger:ignore
|
||||
RandomStats `json:"-"`
|
||||
}
|
||||
|
||||
// InstanceV1URLs models instance-relevant URLs for client application consumption.
|
||||
|
|
|
@ -74,6 +74,13 @@ type InstanceV2 struct {
|
|||
Terms string `json:"terms,omitempty"`
|
||||
// Raw (unparsed) version of terms.
|
||||
TermsText string `json:"terms_text,omitempty"`
|
||||
|
||||
// Random stats generated for the instance.
|
||||
// Only used if `instance-stats-randomize` is true.
|
||||
// Not serialized to the frontend.
|
||||
//
|
||||
// swagger:ignore
|
||||
RandomStats `json:"-"`
|
||||
}
|
||||
|
||||
// Usage data for this instance.
|
||||
|
|
|
@ -90,6 +90,7 @@ type Configuration struct {
|
|||
InstanceLanguages language.Languages `name:"instance-languages" usage:"BCP47 language tags for the instance. Used to indicate the preferred languages of instance residents (in order from most-preferred to least-preferred)."`
|
||||
InstanceSubscriptionsProcessFrom string `name:"instance-subscriptions-process-from" usage:"Time of day from which to start running instance subscriptions processing jobs. Should be in the format 'hh:mm:ss', eg., '15:04:05'."`
|
||||
InstanceSubscriptionsProcessEvery time.Duration `name:"instance-subscriptions-process-every" usage:"Period to elapse between instance subscriptions processing jobs, starting from instance-subscriptions-process-from."`
|
||||
InstanceStatsRandomize bool `name:"instance-stats-randomize" usage:"Set to true to randomize the stats served at api/v1/instance and api/v2/instance endpoints. Home page stats remain unchanged."`
|
||||
|
||||
AccountsRegistrationOpen bool `name:"accounts-registration-open" usage:"Allow anyone to submit an account signup request. If false, server will be invite-only."`
|
||||
AccountsReasonRequired bool `name:"accounts-reason-required" usage:"Do new account signups require a reason to be submitted on registration?"`
|
||||
|
|
|
@ -92,6 +92,7 @@ func (s *ConfigState) AddServerFlags(cmd *cobra.Command) {
|
|||
cmd.Flags().StringSlice(InstanceLanguagesFlag(), cfg.InstanceLanguages.TagStrs(), fieldtag("InstanceLanguages", "usage"))
|
||||
cmd.Flags().String(InstanceSubscriptionsProcessFromFlag(), cfg.InstanceSubscriptionsProcessFrom, fieldtag("InstanceSubscriptionsProcessFrom", "usage"))
|
||||
cmd.Flags().Duration(InstanceSubscriptionsProcessEveryFlag(), cfg.InstanceSubscriptionsProcessEvery, fieldtag("InstanceSubscriptionsProcessEvery", "usage"))
|
||||
cmd.Flags().Bool(InstanceStatsRandomizeFlag(), cfg.InstanceStatsRandomize, fieldtag("InstanceStatsRandomize", "usage"))
|
||||
|
||||
// Accounts
|
||||
cmd.Flags().Bool(AccountsRegistrationOpenFlag(), cfg.AccountsRegistrationOpen, fieldtag("AccountsRegistrationOpen", "usage"))
|
||||
|
|
|
@ -1057,6 +1057,31 @@ func SetInstanceSubscriptionsProcessEvery(v time.Duration) {
|
|||
global.SetInstanceSubscriptionsProcessEvery(v)
|
||||
}
|
||||
|
||||
// GetInstanceStatsRandomize safely fetches the Configuration value for state's 'InstanceStatsRandomize' field
|
||||
func (st *ConfigState) GetInstanceStatsRandomize() (v bool) {
|
||||
st.mutex.RLock()
|
||||
v = st.config.InstanceStatsRandomize
|
||||
st.mutex.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// SetInstanceStatsRandomize safely sets the Configuration value for state's 'InstanceStatsRandomize' field
|
||||
func (st *ConfigState) SetInstanceStatsRandomize(v bool) {
|
||||
st.mutex.Lock()
|
||||
defer st.mutex.Unlock()
|
||||
st.config.InstanceStatsRandomize = v
|
||||
st.reloadToViper()
|
||||
}
|
||||
|
||||
// InstanceStatsRandomizeFlag returns the flag name for the 'InstanceStatsRandomize' field
|
||||
func InstanceStatsRandomizeFlag() string { return "instance-stats-randomize" }
|
||||
|
||||
// GetInstanceStatsRandomize safely fetches the value for global configuration 'InstanceStatsRandomize' field
|
||||
func GetInstanceStatsRandomize() bool { return global.GetInstanceStatsRandomize() }
|
||||
|
||||
// SetInstanceStatsRandomize safely sets the value for global configuration 'InstanceStatsRandomize' field
|
||||
func SetInstanceStatsRandomize(v bool) { global.SetInstanceStatsRandomize(v) }
|
||||
|
||||
// GetAccountsRegistrationOpen safely fetches the Configuration value for state's 'AccountsRegistrationOpen' field
|
||||
func (st *ConfigState) GetAccountsRegistrationOpen() (v bool) {
|
||||
st.mutex.RLock()
|
||||
|
@ -2699,7 +2724,7 @@ func (st *ConfigState) SetAdvancedRateLimitExceptionsParsed(v []netip.Prefix) {
|
|||
}
|
||||
|
||||
// AdvancedRateLimitExceptionsParsedFlag returns the flag name for the 'AdvancedRateLimitExceptionsParsed' field
|
||||
func AdvancedRateLimitExceptionsParsedFlag() string { return "" }
|
||||
func AdvancedRateLimitExceptionsParsedFlag() string { return "advanced-rate-limit-exceptions-parsed" }
|
||||
|
||||
// GetAdvancedRateLimitExceptionsParsed safely fetches the value for global configuration 'AdvancedRateLimitExceptionsParsed' field
|
||||
func GetAdvancedRateLimitExceptionsParsed() []netip.Prefix {
|
||||
|
|
|
@ -65,16 +65,30 @@ func (p *Processor) NodeInfoRelGet(ctx context.Context) (*apimodel.WellKnownResp
|
|||
|
||||
// NodeInfoGet returns a node info struct in response to a node info request.
|
||||
func (p *Processor) NodeInfoGet(ctx context.Context) (*apimodel.Nodeinfo, gtserror.WithCode) {
|
||||
host := config.GetHost()
|
||||
var (
|
||||
userCount int
|
||||
postCount int
|
||||
err error
|
||||
)
|
||||
|
||||
userCount, err := p.state.DB.CountInstanceUsers(ctx, host)
|
||||
if err != nil {
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
if config.GetInstanceStatsRandomize() {
|
||||
// Use randomized stats.
|
||||
stats := p.converter.RandomStats()
|
||||
userCount = int(stats.TotalUsers)
|
||||
postCount = int(stats.Statuses)
|
||||
} else {
|
||||
// Count actual stats.
|
||||
host := config.GetHost()
|
||||
|
||||
postCount, err := p.state.DB.CountInstanceStatuses(ctx, host)
|
||||
if err != nil {
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
userCount, err = p.state.DB.CountInstanceUsers(ctx, host)
|
||||
if err != nil {
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
postCount, err = p.state.DB.CountInstanceStatuses(ctx, host)
|
||||
if err != nil {
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
}
|
||||
|
||||
return &apimodel.Nodeinfo{
|
||||
|
|
|
@ -18,10 +18,17 @@
|
|||
package typeutils
|
||||
|
||||
import (
|
||||
crand "crypto/rand"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/filter/interaction"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/filter/visibility"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/state"
|
||||
)
|
||||
|
||||
|
@ -31,6 +38,7 @@ type Converter struct {
|
|||
randAvatars sync.Map
|
||||
visFilter *visibility.Filter
|
||||
intFilter *interaction.Filter
|
||||
randStats atomic.Pointer[apimodel.RandomStats]
|
||||
}
|
||||
|
||||
func NewConverter(state *state.State) *Converter {
|
||||
|
@ -41,3 +49,53 @@ func NewConverter(state *state.State) *Converter {
|
|||
intFilter: interaction.NewFilter(state),
|
||||
}
|
||||
}
|
||||
|
||||
// RandomStats returns or generates
|
||||
// and returns random instance stats.
|
||||
func (c *Converter) RandomStats() apimodel.RandomStats {
|
||||
now := time.Now()
|
||||
stats := c.randStats.Load()
|
||||
if stats != nil && time.Since(stats.Generated) < time.Hour {
|
||||
// Random stats are still
|
||||
// fresh (less than 1hr old),
|
||||
// so return them as-is.
|
||||
return *stats
|
||||
}
|
||||
|
||||
// Generate new random stats.
|
||||
newStats := genRandStats()
|
||||
newStats.Generated = now
|
||||
c.randStats.Store(&newStats)
|
||||
return newStats
|
||||
}
|
||||
|
||||
func genRandStats() apimodel.RandomStats {
|
||||
const (
|
||||
statusesMax = 10000000
|
||||
usersMax = 1000000
|
||||
)
|
||||
|
||||
statusesB, err := crand.Int(crand.Reader, big.NewInt(statusesMax))
|
||||
if err != nil {
|
||||
// Only errs if something is buggered with the OS.
|
||||
log.Panicf(nil, "error randomly generating statuses count: %v", err)
|
||||
}
|
||||
|
||||
totalUsersB, err := crand.Int(crand.Reader, big.NewInt(usersMax))
|
||||
if err != nil {
|
||||
// Only errs if something is buggered with the OS.
|
||||
log.Panicf(nil, "error randomly generating users count: %v", err)
|
||||
}
|
||||
|
||||
// Monthly users should only ever
|
||||
// be <= 100% of total users.
|
||||
totalUsers := totalUsersB.Int64()
|
||||
activeRatio := rand.Float64() //nolint
|
||||
mau := int64(float64(totalUsers) * activeRatio)
|
||||
|
||||
return apimodel.RandomStats{
|
||||
Statuses: statusesB.Int64(),
|
||||
TotalUsers: totalUsers,
|
||||
MonthlyActiveUsers: mau,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1745,6 +1745,12 @@ func (c *Converter) InstanceToAPIV1Instance(ctx context.Context, i *gtsmodel.Ins
|
|||
stats["domain_count"] = util.Ptr(domainCount)
|
||||
instance.Stats = stats
|
||||
|
||||
if config.GetInstanceStatsRandomize() {
|
||||
// Whack some random stats on the instance
|
||||
// to be injected by API handlers.
|
||||
instance.RandomStats = c.RandomStats()
|
||||
}
|
||||
|
||||
// thumbnail
|
||||
iAccount, err := c.state.DB.GetInstanceAccount(ctx, "")
|
||||
if err != nil {
|
||||
|
@ -1821,6 +1827,12 @@ func (c *Converter) InstanceToAPIV2Instance(ctx context.Context, i *gtsmodel.Ins
|
|||
instance.Debug = util.Ptr(true)
|
||||
}
|
||||
|
||||
if config.GetInstanceStatsRandomize() {
|
||||
// Whack some random stats on the instance
|
||||
// to be injected by API handlers.
|
||||
instance.RandomStats = c.RandomStats()
|
||||
}
|
||||
|
||||
// thumbnail
|
||||
thumbnail := apimodel.InstanceV2Thumbnail{}
|
||||
|
||||
|
|
|
@ -118,6 +118,7 @@ EXPECT=$(cat << "EOF"
|
|||
"nl",
|
||||
"en-GB"
|
||||
],
|
||||
"instance-stats-randomize": true,
|
||||
"instance-subscriptions-process-every": 86400000000000,
|
||||
"instance-subscriptions-process-from": "23:00",
|
||||
"landing-page-user": "admin",
|
||||
|
@ -248,6 +249,7 @@ GTS_INSTANCE_FEDERATION_SPAM_FILTER=true \
|
|||
GTS_INSTANCE_DELIVER_TO_SHARED_INBOXES=false \
|
||||
GTS_INSTANCE_INJECT_MASTODON_VERSION=true \
|
||||
GTS_INSTANCE_LANGUAGES="nl,en-gb" \
|
||||
GTS_INSTANCE_STATS_RANDOMIZE=true \
|
||||
GTS_ACCOUNTS_ALLOW_CUSTOM_CSS=true \
|
||||
GTS_ACCOUNTS_CUSTOM_CSS_LENGTH=5000 \
|
||||
GTS_ACCOUNTS_REGISTRATION_OPEN=true \
|
||||
|
|
Loading…
Reference in a new issue