moved some stuff around, renamed some things
This commit is contained in:
		
							parent
							
								
									fc03989d08
								
							
						
					
					
						commit
						5f00c3441b
					
				
							
								
								
									
										5
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								Makefile
									
									
									
									
									
								
							| @ -46,7 +46,10 @@ fmt: | |||||||
| 
 | 
 | ||||||
| test: build-docker-image | test: build-docker-image | ||||||
| 
 | 
 | ||||||
| build-docker-image: | is_uncommitted: | ||||||
|  | 	git diff --exit-code >/dev/null 2>&1 | ||||||
|  | 
 | ||||||
|  | build-docker-image: is_uncommitted | ||||||
| 	docker build -t $(IMAGENAME):$(VERSION) -t $(IMAGENAME):latest -t $(IMAGENAME):$(BUILDTIMETAG) . | 	docker build -t $(IMAGENAME):$(VERSION) -t $(IMAGENAME):latest -t $(IMAGENAME):$(BUILDTIMETAG) . | ||||||
| 
 | 
 | ||||||
| dist: build-docker-image | dist: build-docker-image | ||||||
|  | |||||||
							
								
								
									
										280
									
								
								instance.go
									
									
									
									
									
								
							
							
						
						
									
										280
									
								
								instance.go
									
									
									
									
									
								
							| @ -2,6 +2,7 @@ package main | |||||||
| 
 | 
 | ||||||
| import "encoding/json" | import "encoding/json" | ||||||
| import "fmt" | import "fmt" | ||||||
|  | import "io/ioutil" | ||||||
| import "net/http" | import "net/http" | ||||||
| import "strings" | import "strings" | ||||||
| import "sync" | import "sync" | ||||||
| @ -11,49 +12,57 @@ import "github.com/rs/zerolog/log" | |||||||
| 
 | 
 | ||||||
| const NodeInfoSchemaVersionTwoName = "http://nodeinfo.diaspora.software/ns/schema/2.0" | const NodeInfoSchemaVersionTwoName = "http://nodeinfo.diaspora.software/ns/schema/2.0" | ||||||
| 
 | 
 | ||||||
| const NODE_TIMEOUT = time.Second * 10 | const INSTANCE_HTTP_TIMEOUT = time.Second * 60 | ||||||
| const ONE_HOUR = time.Second * 60 * 60 |  | ||||||
| const ONE_DAY = time.Second * 60 * 60 * 24 |  | ||||||
| 
 | 
 | ||||||
| type ServerImplementation int | const INSTANCE_SPIDER_INTERVAL = time.Second * 60 | ||||||
|  | 
 | ||||||
|  | const INSTANCE_ERROR_INTERVAL = time.Second * 60 * 30 | ||||||
|  | 
 | ||||||
|  | type InstanceImplementation int | ||||||
| 
 | 
 | ||||||
| const ( | const ( | ||||||
| 	ServerUnknown ServerImplementation = iota | 	Unknown InstanceImplementation = iota | ||||||
| 	ServerMastodon | 	Mastodon | ||||||
| 	ServerPleroma | 	Pleroma | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type InstanceStatus int | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	InstanceStatusNone InstanceStatus = iota | ||||||
|  | 	InstanceStatusUnknown | ||||||
|  | 	InstanceStatusAlive | ||||||
|  | 	InstanceStatusFailure | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type Instance struct { | type Instance struct { | ||||||
| 	sync.Mutex | 	sync.Mutex | ||||||
| 	errorCount    uint | 	errorCount    uint | ||||||
|  | 	successCount  uint | ||||||
| 	highestId     int | 	highestId     int | ||||||
| 	hostName      string | 	hostName      string | ||||||
| 	up            bool |  | ||||||
| 	identified    bool | 	identified    bool | ||||||
| 	impl          ServerImplementation | 	impl          InstanceImplementation | ||||||
| 	lastError     *time.Time | 	status        InstanceStatus | ||||||
| 	lastSuccess   *time.Time |  | ||||||
| 	nextCheck     *time.Time | 	nextCheck     *time.Time | ||||||
| 	nodeInfoUrl   string | 	nodeInfoUrl   string | ||||||
| 	serverVersion string | 	serverVersion string | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func NewInstance(hostname string) *Instance { | func NewInstance(hostname string) *Instance { | ||||||
| 	foreverago := time.Now().Add((-1 * 86400 * 365 * 100) * time.Second) |  | ||||||
| 	i := new(Instance) | 	i := new(Instance) | ||||||
| 	i.hostName = hostname | 	i.hostName = hostname | ||||||
| 	i.nextCheck = &foreverago | 	i.status = InstanceStatusUnknown | ||||||
| 	i.up = false | 	t := time.Now().Add(-1 * time.Second) | ||||||
| 	go func() { | 	i.nextCheck = &t | ||||||
| 		i.detectNodeType() | 	// FIXME make checks detect the node type instead of in the constructor
 | ||||||
| 	}() |  | ||||||
| 	return i | 	return i | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (i *Instance) setNextCheck(d *time.Duration) { | func (i *Instance) setNextCheck(d time.Duration) { | ||||||
| 	i.Lock() | 	i.Lock() | ||||||
| 	defer i.Unlock() | 	defer i.Unlock() | ||||||
| 	then := time.Now().Add(*d) | 	then := time.Now().Add(d) | ||||||
| 	i.nextCheck = &then | 	i.nextCheck = &then | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -65,7 +74,7 @@ func (i *Instance) dueForCheck() bool { | |||||||
| 
 | 
 | ||||||
| func (i *Instance) detectNodeType() { | func (i *Instance) detectNodeType() { | ||||||
| 	i.Lock() | 	i.Lock() | ||||||
| 	if i.impl > ServerUnknown { | 	if i.impl > Unknown { | ||||||
| 		i.Unlock() | 		i.Unlock() | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| @ -73,50 +82,24 @@ func (i *Instance) detectNodeType() { | |||||||
| 	i.fetchNodeInfo() | 	i.fetchNodeInfo() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type NodeInfoWellKnownResponse struct { |  | ||||||
| 	Links []struct { |  | ||||||
| 		Rel  string `json:"rel"` |  | ||||||
| 		Href string `json:"href"` |  | ||||||
| 	} `json:"links"` |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| type NodeInfoVersionTwoSchema struct { |  | ||||||
| 	Version  string `json:"version"` |  | ||||||
| 	Software struct { |  | ||||||
| 		Name    string `json:"name"` |  | ||||||
| 		Version string `json:"version"` |  | ||||||
| 	} `json:"software"` |  | ||||||
| 	Protocols []string `json:"protocols"` |  | ||||||
| 	Usage     struct { |  | ||||||
| 		Users struct { |  | ||||||
| 			Total          int `json:"total"` |  | ||||||
| 			ActiveMonth    int `json:"activeMonth"` |  | ||||||
| 			ActiveHalfyear int `json:"activeHalfyear"` |  | ||||||
| 		} `json:"users"` |  | ||||||
| 		LocalPosts int `json:"localPosts"` |  | ||||||
| 	} `json:"usage"` |  | ||||||
| 	OpenRegistrations bool `json:"openRegistrations"` |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (i *Instance) registerError() { | func (i *Instance) registerError() { | ||||||
|  | 	i.setNextCheck(INSTANCE_ERROR_INTERVAL) | ||||||
| 	i.Lock() | 	i.Lock() | ||||||
| 	defer i.Unlock() | 	defer i.Unlock() | ||||||
| 	i.errorCount = i.errorCount + 1 | 	i.errorCount++ | ||||||
| 	t := time.Now() |  | ||||||
| 	i.lastError = &t |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (i *Instance) registerSuccess() { | func (i *Instance) registerSuccess() { | ||||||
|  | 	i.setNextCheck(INSTANCE_SPIDER_INTERVAL) | ||||||
| 	i.Lock() | 	i.Lock() | ||||||
| 	defer i.Unlock() | 	defer i.Unlock() | ||||||
| 	t := time.Now() | 	i.successCount++ | ||||||
| 	i.lastSuccess = &t |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (i *Instance) fetchNodeInfoURL() { | func (i *Instance) fetchNodeInfoURL() { | ||||||
| 	url := fmt.Sprintf("https://%s/.well-known/nodeinfo", i.hostName) | 	url := fmt.Sprintf("https://%s/.well-known/nodeinfo", i.hostName) | ||||||
| 	var c = &http.Client{ | 	var c = &http.Client{ | ||||||
| 		Timeout: NODE_TIMEOUT, | 		Timeout: INSTANCE_HTTP_TIMEOUT, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	log.Debug(). | 	log.Debug(). | ||||||
| @ -126,41 +109,57 @@ func (i *Instance) fetchNodeInfoURL() { | |||||||
| 
 | 
 | ||||||
| 	resp, err := c.Get(url) | 	resp, err := c.Get(url) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		log.Error(). | 		log.Debug(). | ||||||
| 			Str("hostname", i.hostName). | 			Str("hostname", i.hostName). | ||||||
|  | 			Err(err). | ||||||
| 			Msg("unable to fetch nodeinfo, node is down?") | 			Msg("unable to fetch nodeinfo, node is down?") | ||||||
| 		i.registerError() | 		i.registerError() | ||||||
| 	} else { |  | ||||||
| 		i.up = true // node is alive and responding to us
 |  | ||||||
| 		nir := new(NodeInfoWellKnownResponse) |  | ||||||
| 		err = json.NewDecoder(resp.Body).Decode(&nir) |  | ||||||
| 		if err != nil { |  | ||||||
| 			log.Error(). |  | ||||||
| 				Str("hostname", i.hostName). |  | ||||||
| 				Msg("unable to parse nodeinfo") |  | ||||||
| 			i.registerError() |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 		for _, item := range nir.Links { |  | ||||||
| 			if item.Rel == NodeInfoSchemaVersionTwoName { |  | ||||||
| 				log.Info(). |  | ||||||
| 					Str("hostname", i.hostName). |  | ||||||
| 					Str("nodeinfourl", item.Href). |  | ||||||
| 					Msg("success fetching url for nodeinfo") |  | ||||||
| 
 |  | ||||||
| 				i.Lock() |  | ||||||
| 				i.nodeInfoUrl = item.Href |  | ||||||
| 				i.Unlock() |  | ||||||
| 				i.registerSuccess() |  | ||||||
| 				return |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		log.Error(). |  | ||||||
| 			Str("hostname", i.hostName). |  | ||||||
| 			Msg("incomplete nodeinfo") |  | ||||||
| 		i.registerError() |  | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	defer resp.Body.Close() | ||||||
|  | 	body, err := ioutil.ReadAll(resp.Body) | ||||||
|  | 
 | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Debug(). | ||||||
|  | 			Str("hostname", i.hostName). | ||||||
|  | 			Err(err). | ||||||
|  | 			Msg("unable to read nodeinfo") | ||||||
|  | 		i.registerError() | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	nir := new(NodeInfoWellKnownResponse) | ||||||
|  | 	err = json.Unmarshal(body, &nir) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Error(). | ||||||
|  | 			Str("hostname", i.hostName). | ||||||
|  | 			Err(err). | ||||||
|  | 			Msg("unable to parse nodeinfo, node is weird") | ||||||
|  | 		i.registerError() | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, item := range nir.Links { | ||||||
|  | 		if item.Rel == NodeInfoSchemaVersionTwoName { | ||||||
|  | 			log.Info(). | ||||||
|  | 				Str("hostname", i.hostName). | ||||||
|  | 				Str("nodeinfourl", item.Href). | ||||||
|  | 				Msg("success fetching url for nodeinfo") | ||||||
|  | 
 | ||||||
|  | 			i.Lock() | ||||||
|  | 			i.nodeInfoUrl = item.Href | ||||||
|  | 			i.Unlock() | ||||||
|  | 			i.registerSuccess() | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	log.Error(). | ||||||
|  | 		Str("hostname", i.hostName). | ||||||
|  | 		Msg("incomplete nodeinfo") | ||||||
|  | 	i.registerError() | ||||||
|  | 	return | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (i *Instance) fetchNodeInfo() { | func (i *Instance) fetchNodeInfo() { | ||||||
| @ -181,7 +180,7 @@ func (i *Instance) fetchNodeInfo() { | |||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	var c = &http.Client{ | 	var c = &http.Client{ | ||||||
| 		Timeout: NODE_TIMEOUT, | 		Timeout: INSTANCE_HTTP_TIMEOUT, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	//FIXME make sure the nodeinfourl is on the same domain as the instance
 | 	//FIXME make sure the nodeinfourl is on the same domain as the instance
 | ||||||
| @ -195,57 +194,74 @@ func (i *Instance) fetchNodeInfo() { | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		log.Error(). | 		log.Error(). | ||||||
| 			Str("hostname", i.hostName). | 			Str("hostname", i.hostName). | ||||||
| 			Msgf("unable to fetch nodeinfo data: %s", err) | 			Err(err). | ||||||
|  | 			Msgf("unable to fetch nodeinfo data") | ||||||
| 		i.registerError() | 		i.registerError() | ||||||
| 	} else { |  | ||||||
| 		ni := new(NodeInfoVersionTwoSchema) |  | ||||||
| 		err = json.NewDecoder(resp.Body).Decode(&ni) |  | ||||||
| 		if err != nil { |  | ||||||
| 			log.Error(). |  | ||||||
| 				Str("hostname", i.hostName). |  | ||||||
| 				Msgf("unable to parse nodeinfo: %s", err) |  | ||||||
| 			i.registerError() |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		log.Info(). |  | ||||||
| 			Str("serverVersion", ni.Software.Version). |  | ||||||
| 			Str("software", ni.Software.Name). |  | ||||||
| 			Str("hostName", i.hostName). |  | ||||||
| 			Str("nodeInfoUrl", i.nodeInfoUrl). |  | ||||||
| 			Msg("received nodeinfo from instance") |  | ||||||
| 
 |  | ||||||
| 		i.Lock() |  | ||||||
| 		defer i.Unlock() |  | ||||||
| 		i.serverVersion = ni.Software.Version |  | ||||||
| 
 |  | ||||||
| 		ni.Software.Name = strings.ToLower(ni.Software.Name) |  | ||||||
| 
 |  | ||||||
| 		if ni.Software.Name == "pleroma" { |  | ||||||
| 			log.Info(). |  | ||||||
| 				Str("hostname", i.hostName). |  | ||||||
| 				Str("software", ni.Software.Name). |  | ||||||
| 				Msg("detected server software") |  | ||||||
| 			i.registerSuccess() |  | ||||||
| 			i.identified = true |  | ||||||
| 			i.impl = ServerPleroma |  | ||||||
| 		} else if ni.Software.Name == "mastodon" { |  | ||||||
| 			log.Info(). |  | ||||||
| 				Str("hostname", i.hostName). |  | ||||||
| 				Str("software", ni.Software.Name). |  | ||||||
| 				Msg("detected server software") |  | ||||||
| 			i.registerSuccess() |  | ||||||
| 			i.identified = true |  | ||||||
| 			i.impl = ServerMastodon |  | ||||||
| 		} else { |  | ||||||
| 			log.Error(). |  | ||||||
| 				Str("hostname", i.hostName). |  | ||||||
| 				Str("software", ni.Software.Name). |  | ||||||
| 				Msg("unknown implementation on server") |  | ||||||
| 			i.registerError() |  | ||||||
| 		} |  | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	defer resp.Body.Close() | ||||||
|  | 	body, err := ioutil.ReadAll(resp.Body) | ||||||
|  | 
 | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Error(). | ||||||
|  | 			Str("hostname", i.hostName). | ||||||
|  | 			Err(err). | ||||||
|  | 			Msgf("unable to read nodeinfo data") | ||||||
|  | 		i.registerError() | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	ni := new(NodeInfoVersionTwoSchema) | ||||||
|  | 	err = json.Unmarshal(body, &ni) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Error(). | ||||||
|  | 			Str("hostname", i.hostName). | ||||||
|  | 			Err(err). | ||||||
|  | 			Msgf("unable to parse nodeinfo") | ||||||
|  | 		i.registerError() | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	log.Info(). | ||||||
|  | 		Str("serverVersion", ni.Software.Version). | ||||||
|  | 		Str("software", ni.Software.Name). | ||||||
|  | 		Str("hostName", i.hostName). | ||||||
|  | 		Str("nodeInfoUrl", i.nodeInfoUrl). | ||||||
|  | 		Msg("received nodeinfo from instance") | ||||||
|  | 
 | ||||||
|  | 	i.Lock() | ||||||
|  | 	defer i.Unlock() | ||||||
|  | 	i.serverVersion = ni.Software.Version | ||||||
|  | 
 | ||||||
|  | 	ni.Software.Name = strings.ToLower(ni.Software.Name) | ||||||
|  | 
 | ||||||
|  | 	if ni.Software.Name == "pleroma" { | ||||||
|  | 		log.Info(). | ||||||
|  | 			Str("hostname", i.hostName). | ||||||
|  | 			Str("software", ni.Software.Name). | ||||||
|  | 			Msg("detected server software") | ||||||
|  | 		i.registerSuccess() | ||||||
|  | 		i.identified = true | ||||||
|  | 		i.impl = Pleroma | ||||||
|  | 		i.status = InstanceStatusAlive | ||||||
|  | 	} else if ni.Software.Name == "mastodon" { | ||||||
|  | 		log.Info(). | ||||||
|  | 			Str("hostname", i.hostName). | ||||||
|  | 			Str("software", ni.Software.Name). | ||||||
|  | 			Msg("detected server software") | ||||||
|  | 		i.registerSuccess() | ||||||
|  | 		i.identified = true | ||||||
|  | 		i.impl = Mastodon | ||||||
|  | 		i.status = InstanceStatusAlive | ||||||
|  | 	} else { | ||||||
|  | 		log.Error(). | ||||||
|  | 			Str("hostname", i.hostName). | ||||||
|  | 			Str("software", ni.Software.Name). | ||||||
|  | 			Msg("unknown implementation on server") | ||||||
|  | 		i.registerError() | ||||||
|  | 	} | ||||||
|  | 	return | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (i *Instance) fetchRecentToots() ([]byte, error) { | func (i *Instance) fetchRecentToots() ([]byte, error) { | ||||||
| @ -253,9 +269,9 @@ func (i *Instance) fetchRecentToots() ([]byte, error) { | |||||||
| 	impl := i.impl | 	impl := i.impl | ||||||
| 	i.Unlock() | 	i.Unlock() | ||||||
| 
 | 
 | ||||||
| 	if impl == ServerMastodon { | 	if impl == Mastodon { | ||||||
| 		return i.fetchRecentTootsJsonFromMastodon() | 		return i.fetchRecentTootsJsonFromMastodon() | ||||||
| 	} else if impl == ServerPleroma { | 	} else if impl == Pleroma { | ||||||
| 		return i.fetchRecentTootsJsonFromPleroma() | 		return i.fetchRecentTootsJsonFromPleroma() | ||||||
| 	} else { | 	} else { | ||||||
| 		panic("unimplemented") | 		panic("unimplemented") | ||||||
|  | |||||||
							
								
								
									
										88
									
								
								jsonapis.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								jsonapis.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,88 @@ | |||||||
|  | package main | ||||||
|  | 
 | ||||||
|  | import "time" | ||||||
|  | 
 | ||||||
|  | // 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"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 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 NodeInfoVersionTwoSchema struct { | ||||||
|  | 	Version  string `json:"version"` | ||||||
|  | 	Software struct { | ||||||
|  | 		Name    string `json:"name"` | ||||||
|  | 		Version string `json:"version"` | ||||||
|  | 	} `json:"software"` | ||||||
|  | 	Protocols []string `json:"protocols"` | ||||||
|  | 	Usage     struct { | ||||||
|  | 		Users struct { | ||||||
|  | 			Total          int `json:"total"` | ||||||
|  | 			ActiveMonth    int `json:"activeMonth"` | ||||||
|  | 			ActiveHalfyear int `json:"activeHalfyear"` | ||||||
|  | 		} `json:"users"` | ||||||
|  | 		LocalPosts int `json:"localPosts"` | ||||||
|  | 	} `json:"usage"` | ||||||
|  | 	OpenRegistrations bool `json:"openRegistrations"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type NodeInfoWellKnownResponse struct { | ||||||
|  | 	Links []struct { | ||||||
|  | 		Rel  string `json:"rel"` | ||||||
|  | 		Href string `json:"href"` | ||||||
|  | 	} `json:"links"` | ||||||
|  | } | ||||||
							
								
								
									
										240
									
								
								locator.go
									
									
									
									
									
								
							
							
						
						
									
										240
									
								
								locator.go
									
									
									
									
									
								
							| @ -2,84 +2,27 @@ package main | |||||||
| 
 | 
 | ||||||
| import "encoding/json" | import "encoding/json" | ||||||
| import "fmt" | import "fmt" | ||||||
|  | import "io/ioutil" | ||||||
| import "net/http" | import "net/http" | ||||||
| import "sync" | import "sync" | ||||||
| import "time" | import "time" | ||||||
| 
 | 
 | ||||||
| import "github.com/rs/zerolog/log" | import "github.com/rs/zerolog/log" | ||||||
| 
 | 
 | ||||||
| const mastodonIndexUrl = "https://instances.social/list.json?q%5Busers%5D=&q%5Bsearch%5D=&strict=false" | const INDEX_API_TIMEOUT = time.Second * 60 | ||||||
| 
 |  | ||||||
| var foreverago = time.Now().Add((-1 * 86400 * 365 * 100) * time.Second) |  | ||||||
| 
 | 
 | ||||||
| // check with indices only hourly
 | // check with indices only hourly
 | ||||||
| var INDEX_CHECK_INTERVAL = time.Second * 60 * 60 | var INDEX_CHECK_INTERVAL = time.Second * 60 * 60 | ||||||
| 
 | 
 | ||||||
| // thank fuck for https://mholt.github.io/json-to-go/ otherwise
 | // check with indices after 10 mins if they failed
 | ||||||
| // this would have been a giant pain in the dick
 | var INDEX_ERROR_INTERVAL = time.Second * 60 * 10 | ||||||
| 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 mastodonIndexUrl = "https://instances.social/list.json?q%5Busers%5D=&q%5Bsearch%5D=&strict=false" | ||||||
| const pleromaIndexUrl = "https://distsn.org/cgi-bin/distsn-pleroma-instances-api.cgi" | 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 { | type InstanceLocator struct { | ||||||
| 	pleromaIndexLastRefresh  *time.Time | 	pleromaIndexNextRefresh  *time.Time | ||||||
| 	mastodonIndexLastRefresh *time.Time | 	mastodonIndexNextRefresh *time.Time | ||||||
| 	instances                map[string]*Instance | 	instances                map[string]*Instance | ||||||
| 	sync.Mutex | 	sync.Mutex | ||||||
| } | } | ||||||
| @ -87,8 +30,9 @@ type InstanceLocator struct { | |||||||
| func NewInstanceLocator() *InstanceLocator { | func NewInstanceLocator() *InstanceLocator { | ||||||
| 	i := new(InstanceLocator) | 	i := new(InstanceLocator) | ||||||
| 	i.instances = make(map[string]*Instance) | 	i.instances = make(map[string]*Instance) | ||||||
| 	i.pleromaIndexLastRefresh = &foreverago | 	n := time.Now() | ||||||
| 	i.mastodonIndexLastRefresh = &foreverago | 	i.pleromaIndexNextRefresh = &n | ||||||
|  | 	i.mastodonIndexNextRefresh = &n | ||||||
| 	return i | 	return i | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -97,35 +41,44 @@ func (i *InstanceLocator) addInstance(hostname string) { | |||||||
| 	defer i.Unlock() | 	defer i.Unlock() | ||||||
| 	// only add it if we haven't seen the hostname before
 | 	// only add it if we haven't seen the hostname before
 | ||||||
| 	if i.instances[hostname] == nil { | 	if i.instances[hostname] == nil { | ||||||
| 		log.Debug().Str("hostname", hostname).Msgf("adding discovered instance") | 		log.Info().Str("hostname", hostname).Msgf("adding discovered instance") | ||||||
| 		i.instances[hostname] = NewInstance(hostname) | 		i.instances[hostname] = NewInstance(hostname) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (i *InstanceLocator) Locate() { | func (i *InstanceLocator) Locate() { | ||||||
| 	log.Debug(). | 	x := 0 | ||||||
| 		Str("lastmastodonupdate", i.mastodonIndexLastRefresh.Format(time.RFC3339)). | 	for { | ||||||
| 		Send() | 		if i.pleromaIndexNextRefresh.Before(time.Now()) { | ||||||
|  | 			i.locatePleroma() | ||||||
|  | 		} | ||||||
|  | 		if i.mastodonIndexNextRefresh.Before(time.Now()) { | ||||||
|  | 			i.locateMastodon() | ||||||
|  | 		} | ||||||
|  | 		time.Sleep(1 * time.Second) | ||||||
|  | 		x++ | ||||||
|  | 		if x == 60 { | ||||||
|  | 			x = 0 | ||||||
|  | 			log.Debug(). | ||||||
|  | 				Str("nextmastodonupdate", i.mastodonIndexNextRefresh.Format(time.RFC3339)). | ||||||
|  | 				Send() | ||||||
|  | 			log.Debug(). | ||||||
|  | 				Str("nextpleromaupdate", i.pleromaIndexNextRefresh.Format(time.RFC3339)). | ||||||
|  | 				Send() | ||||||
|  | 			i.logInstanceReport() | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	log.Debug(). | } | ||||||
| 		Str("lastpleromaupdate", i.pleromaIndexLastRefresh.Format(time.RFC3339)). |  | ||||||
| 		Send() |  | ||||||
| 
 |  | ||||||
| 	i.locateMastodon() |  | ||||||
| 
 |  | ||||||
| 	i.locatePleroma() |  | ||||||
| 
 |  | ||||||
| 	time.Sleep(120 * time.Second) |  | ||||||
| 
 | 
 | ||||||
|  | func (i *InstanceLocator) logInstanceReport() { | ||||||
| 	r := i.instanceReport() | 	r := i.instanceReport() | ||||||
| 
 | 	log.Info(). | ||||||
| 	log.Debug(). |  | ||||||
| 		Uint("up", r.up). | 		Uint("up", r.up). | ||||||
| 		Uint("total", r.total). | 		Uint("total", r.total). | ||||||
| 		Uint("identified", r.identified). | 		Uint("identified", r.identified). | ||||||
| 		Msg("instance report") | 		Msg("instance report") | ||||||
| 
 |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type InstanceLocatorReport struct { | type InstanceLocatorReport struct { | ||||||
| @ -154,7 +107,7 @@ func (i *InstanceLocator) instanceReport() *InstanceLocatorReport { | |||||||
| 			r.identified = r.identified + 1 | 			r.identified = r.identified + 1 | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if elem.up == true { | 		if elem.status == InstanceStatusAlive { | ||||||
| 			r.up = r.up + 1 | 			r.up = r.up + 1 | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| @ -163,54 +116,97 @@ func (i *InstanceLocator) instanceReport() *InstanceLocatorReport { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (i *InstanceLocator) locateMastodon() { | func (i *InstanceLocator) locateMastodon() { | ||||||
| 	var netClient = &http.Client{ | 	var c = &http.Client{ | ||||||
| 		Timeout: NODE_TIMEOUT, | 		Timeout: INDEX_API_TIMEOUT, | ||||||
| 	} | 	} | ||||||
| 	resp, err := netClient.Get(mastodonIndexUrl) | 
 | ||||||
|  | 	resp, err := c.Get(mastodonIndexUrl) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Error().Msgf("unable to fetch mastodon instance list: %s", err) | ||||||
|  | 		t := time.Now().Add(INDEX_ERROR_INTERVAL) | ||||||
|  | 		i.Lock() | ||||||
|  | 		i.mastodonIndexNextRefresh = &t | ||||||
|  | 		i.Unlock() | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	defer resp.Body.Close() | 	defer resp.Body.Close() | ||||||
|  | 	body, err := ioutil.ReadAll(resp.Body) | ||||||
| 
 | 
 | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		log.Warn().Msgf("unable to fetch mastodon instance list: %s", err) | 		log.Error().Msgf("unable to fetch mastodon instance list: %s", err) | ||||||
| 	} else { | 		t := time.Now().Add(INDEX_ERROR_INTERVAL) | ||||||
| 		// it worked
 | 		i.Lock() | ||||||
| 		mi := new(MastodonIndexResponse) | 		i.mastodonIndexNextRefresh = &t | ||||||
| 		err = json.NewDecoder(resp.Body).Decode(&mi) | 		i.Unlock() | ||||||
| 		if err != nil { | 		return | ||||||
| 			log.Warn().Msgf("unable to parse mastodon instance list: %s", err) |  | ||||||
| 		} else { |  | ||||||
| 			for _, instance := range mi.Instances { |  | ||||||
| 				i.addInstance(instance.Name) |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			t := time.Now() |  | ||||||
| 			i.Lock() |  | ||||||
| 			i.mastodonIndexLastRefresh = &t |  | ||||||
| 			i.Unlock() |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	mi := new(MastodonIndexResponse) | ||||||
|  | 	err = json.Unmarshal(body, &mi) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Error().Msgf("unable to parse mastodon instance list: %s", err) | ||||||
|  | 		t := time.Now().Add(INDEX_ERROR_INTERVAL) | ||||||
|  | 		i.Lock() | ||||||
|  | 		i.mastodonIndexNextRefresh = &t | ||||||
|  | 		i.Unlock() | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, instance := range mi.Instances { | ||||||
|  | 		i.addInstance(instance.Name) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	t := time.Now().Add(INDEX_CHECK_INTERVAL) | ||||||
|  | 	i.Lock() | ||||||
|  | 	i.mastodonIndexNextRefresh = &t | ||||||
|  | 	i.Unlock() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (i *InstanceLocator) locatePleroma() { | func (i *InstanceLocator) locatePleroma() { | ||||||
| 	var netClient = &http.Client{ | 	var c = &http.Client{ | ||||||
| 		Timeout: NODE_TIMEOUT, | 		Timeout: INDEX_API_TIMEOUT, | ||||||
| 	} | 	} | ||||||
| 	resp, err := netClient.Get(pleromaIndexUrl) | 	resp, err := c.Get(pleromaIndexUrl) | ||||||
|  | 
 | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		log.Warn().Msgf("unable to fetch pleroma instance list: %s", err) | 		log.Error().Msgf("unable to fetch pleroma instance list: %s", err) | ||||||
| 	} else { | 		t := time.Now().Add(INDEX_ERROR_INTERVAL) | ||||||
| 		// fetch worked
 | 		i.Lock() | ||||||
| 		pi := new(PleromaIndexResponse) | 		i.pleromaIndexNextRefresh = &t | ||||||
| 		err = json.NewDecoder(resp.Body).Decode(&pi) | 		i.Unlock() | ||||||
| 		if err != nil { | 		return | ||||||
| 			log.Warn().Msgf("unable to parse pleroma instance list: %s", err) |  | ||||||
| 		} else { |  | ||||||
| 			for _, instance := range *pi { |  | ||||||
| 				i.addInstance(instance.Domain) |  | ||||||
| 			} |  | ||||||
| 			t := time.Now() |  | ||||||
| 			i.Lock() |  | ||||||
| 			i.pleromaIndexLastRefresh = &t |  | ||||||
| 			i.Unlock() |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	defer resp.Body.Close() | ||||||
|  | 	body, err := ioutil.ReadAll(resp.Body) | ||||||
|  | 
 | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Error().Msgf("unable to fetch pleroma instance list: %s", err) | ||||||
|  | 		t := time.Now().Add(INDEX_ERROR_INTERVAL) | ||||||
|  | 		i.Lock() | ||||||
|  | 		i.pleromaIndexNextRefresh = &t | ||||||
|  | 		i.Unlock() | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// fetch worked
 | ||||||
|  | 	pi := new(PleromaIndexResponse) | ||||||
|  | 	err = json.Unmarshal(body, &pi) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Warn().Msgf("unable to parse pleroma instance list: %s", err) | ||||||
|  | 		t := time.Now().Add(INDEX_ERROR_INTERVAL) | ||||||
|  | 		i.Lock() | ||||||
|  | 		i.pleromaIndexNextRefresh = &t | ||||||
|  | 		i.Unlock() | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, instance := range *pi { | ||||||
|  | 		i.addInstance(instance.Domain) | ||||||
|  | 	} | ||||||
|  | 	t := time.Now().Add(INDEX_CHECK_INTERVAL) | ||||||
|  | 	i.Lock() | ||||||
|  | 	i.pleromaIndexNextRefresh = &t | ||||||
|  | 	i.Unlock() | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user