package main import ( "encoding/json" "github.com/rs/zerolog/log" "net/http" "sync" "time" ) const mastodonIndexUrl = "https://instances.social/list.json?q%5Busers%5D=&q%5Bsearch%5D=&strict=false" var foreverago = time.Now().Add((-1 * 86400 * 365 * 100) * time.Second) // check with indices only hourly var INDEX_CHECK_INTERVAL = time.Second * 60 * 60 // thank fuck for https://mholt.github.io/json-to-go/ otherwise // this would have been a giant pain in the dick type MastodonIndexResponse struct { Instances []struct { ID string `json:"_id"` AddedAt time.Time `json:"addedAt"` Name string `json:"name"` Downchecks int `json:"downchecks"` Upchecks int `json:"upchecks"` HTTPSRank interface{} `json:"https_rank"` HTTPSScore int `json:"https_score"` ObsRank string `json:"obs_rank"` ObsScore int `json:"obs_score"` Ipv6 bool `json:"ipv6"` Up bool `json:"up"` Users int `json:"users"` Statuses string `json:"statuses"` Connections int `json:"connections"` OpenRegistrations bool `json:"openRegistrations"` Uptime float64 `json:"uptime"` Version string `json:"version"` VersionScore int `json:"version_score"` UpdatedAt time.Time `json:"updatedAt"` CheckedAt time.Time `json:"checkedAt"` Dead bool `json:"dead"` ObsDate time.Time `json:"obs_date"` Second60 int `json:"second60"` Second int `json:"second"` ActiveUserCount interface{} `json:"active_user_count,omitempty"` FirstUserCreatedAt interface{} `json:"first_user_created_at,omitempty"` Thumbnail string `json:"thumbnail"` ApUpdatedAt time.Time `json:"apUpdatedAt"` Second5 int `json:"second5"` RawVersion string `json:"raw_version"` ActivityPrevw struct { Statuses int `json:"statuses"` Logins int `json:"logins"` Registrations int `json:"registrations"` } `json:"activity_prevw,omitempty"` Mastodon bool `json:"mastodon"` UptimeStr string `json:"uptime_str"` Score int `json:"score"` ScoreStr string `json:"score_str"` } `json:"instances"` } const pleromaIndexUrl = "https://distsn.org/cgi-bin/distsn-pleroma-instances-api.cgi" type PleromaIndexResponse []struct { Domain string `json:"domain"` Title string `json:"title"` Thumbnail string `json:"thumbnail"` Registration bool `json:"registration"` Chat bool `json:"chat"` Gopher bool `json:"gopher"` WhoToFollow bool `json:"who_to_follow"` MediaProxy bool `json:"media_proxy"` ScopeOptions bool `json:"scope_options"` AccountActivationRequired bool `json:"account_activation_required"` TextLimit int `json:"text_limit"` } type InstanceLocator struct { pleromaIndexLastRefresh *time.Time mastodonIndexLastRefresh *time.Time instances map[string]*Instance sync.Mutex } func NewInstanceLocator() *InstanceLocator { i := new(InstanceLocator) i.instances = make(map[string]*Instance) i.pleromaIndexLastRefresh = &foreverago i.mastodonIndexLastRefresh = &foreverago return i } func (i *InstanceLocator) addInstance(hostname string) { // only add it if we haven't seen the hostname before if i.instances[hostname] == nil { log.Debug().Str("hostname", hostname).Msgf("adding discovered instance") i.Lock() i.instances[hostname] = NewInstance(hostname) i.Unlock() } } func (i *InstanceLocator) Locate() { log.Debug().Str("lastmastodonupdate", i.mastodonIndexLastRefresh.Format(time.RFC3339)).Send() log.Debug().Str("lastpleromaupdate", i.pleromaIndexLastRefresh.Format(time.RFC3339)).Send() i.locateMastodon() i.locatePleroma() time.Sleep(120 * time.Second) i.instanceReport() } func (i *InstanceLocator) instanceReport() { var upInstances int = 0 var identifiedInstances int = 0 var totalInstances int = 0 totalInstances = len(i.instances) for _, elem := range i.instances { if elem.identified == true { identifiedInstances = identifiedInstances + 1 } } for _, elem := range i.instances { if elem.up == true { upInstances = upInstances + 1 } } log.Info(). Int("up", upInstances). Int("total", totalInstances). Int("identified", identifiedInstances). Msg("instance report") } func (i *InstanceLocator) locateMastodon() { var netClient = &http.Client{ Timeout: time.Second * 20, } resp, err := netClient.Get(mastodonIndexUrl) defer resp.Body.Close() if err != nil { log.Warn().Msgf("unable to fetch mastodon instance list: %s", err) } else { // it worked mi := new(MastodonIndexResponse) err = json.NewDecoder(resp.Body).Decode(&mi) if err != nil { log.Warn().Msgf("unable to parse mastodon instance list: %s", err) } else { for _, instance := range mi.Instances { i.addInstance(instance.Name) } i.Lock() t := time.Now() i.mastodonIndexLastRefresh = &t i.Unlock() } } } func (i *InstanceLocator) locatePleroma() { var netClient = &http.Client{ Timeout: time.Second * 20, } resp, err := netClient.Get(pleromaIndexUrl) if err != nil { log.Warn().Msgf("unable to fetch pleroma instance list: %s", err) } else { // fetch worked pi := new(PleromaIndexResponse) err = json.NewDecoder(resp.Body).Decode(&pi) if err != nil { log.Warn().Msgf("unable to parse pleroma instance list: %s", err) } else { for _, instance := range *pi { i.addInstance(instance.Domain) } i.Lock() t := time.Now() i.pleromaIndexLastRefresh = &t i.Unlock() } } }