From b5bad4defc6c75b9b969658229ce5fd2f3a46107 Mon Sep 17 00:00:00 2001 From: Ben Busby Date: Tue, 21 Jan 2025 13:46:29 -0700 Subject: Rewrite project, add daily update of services list The project was rewritten from Elixir to Go, primarily because: - I don't write Elixir anymore and don't want to maintain a project in a language I no longer write - I already write Go for other projects, including my day job, so it's a safer bet for a project that I want to maintain long term - Go allows me to build portable executables that will make it easier for others to run farside on their own machines The Go version of Farsside also has a built in task to fetch the latest services{-full}.json file from the repo and ingest it, which makes running a farside server a lot simpler. It also automatically fetches the latest instance state from https://farside.link unless configured as a primary farside node, which will allow others to use farside without increasing traffic to all instances that are queried by farside (just to the farside node itself). --- services/mappings.go | 110 +++++++++++++++++++++++++++++++++++++++++++++++++++ services/services.go | 93 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 203 insertions(+) create mode 100644 services/mappings.go create mode 100644 services/services.go (limited to 'services') diff --git a/services/mappings.go b/services/mappings.go new file mode 100644 index 0000000..53731a6 --- /dev/null +++ b/services/mappings.go @@ -0,0 +1,110 @@ +package services + +import ( + "errors" + "math/rand" + "regexp" +) + +type RegexMapping struct { + Pattern *regexp.Regexp + Targets []string +} + +var regexMap = []RegexMapping{ + { + // YouTube + Pattern: regexp.MustCompile(`youtu(\.be|be\.com)|invidious|piped`), + Targets: []string{"piped", "invidious"}, + }, + { + // Twitter / X + Pattern: regexp.MustCompile(`twitter\.com|x\.com|nitter`), + Targets: []string{"nitter"}, + }, + { + // Reddit + Pattern: regexp.MustCompile(`reddit\.com|libreddit|redlib`), + Targets: []string{"libreddit", "redlib"}, + }, + { + // Google Search + Pattern: regexp.MustCompile(`google\.com|whoogle|searx|searxng`), + Targets: []string{"whoogle", "searx", "searxng"}, + }, + { + // Instagram + Pattern: regexp.MustCompile(`instagram\.com|proxigram`), + Targets: []string{"proxigram"}, + }, + { + // Wikipedia + Pattern: regexp.MustCompile(`wikipedia\.org|wikiless`), + Targets: []string{"wikiless"}, + }, + { + // Medium + Pattern: regexp.MustCompile(`medium\.com|scribe`), + Targets: []string{"scribe"}, + }, + { + // Odysee + Pattern: regexp.MustCompile(`odysee\.com|librarian`), + Targets: []string{"librarian"}, + }, + { + // Imgur + Pattern: regexp.MustCompile(`imgur\.com|rimgo`), + Targets: []string{"rimgo"}, + }, + { + // Google Translate + Pattern: regexp.MustCompile(`translate\.google\.com|lingva`), + Targets: []string{"lingva"}, + }, + { + // TikTok + Pattern: regexp.MustCompile(`tiktok\.com|proxitok`), + Targets: []string{"proxitok"}, + }, + { + // Fandom + Pattern: regexp.MustCompile(`fandom\.com|breezewiki`), + Targets: []string{"breezewiki"}, + }, + { + // IMDB + Pattern: regexp.MustCompile(`imdb\.com|libremdb`), + Targets: []string{"libremdb"}, + }, + { + // Quora + Pattern: regexp.MustCompile(`quora\.com|quetre`), + Targets: []string{"quetre"}, + }, + { + // GitHub + Pattern: regexp.MustCompile(`github\.com|gothub`), + Targets: []string{"gothub"}, + }, + { + // StackOverflow + Pattern: regexp.MustCompile(`stackoverflow\.com|anonymousoverflow`), + Targets: []string{"anonymousoverflow"}, + }, +} + +func MatchRequest(service string) (string, error) { + for _, mapping := range regexMap { + hasMatch := mapping.Pattern.MatchString(service) + if !hasMatch { + continue + } + + index := rand.Intn(len(mapping.Targets)) + value := mapping.Targets[index] + return value, nil + } + + return "", errors.New("no match found") +} diff --git a/services/services.go b/services/services.go new file mode 100644 index 0000000..b9e5693 --- /dev/null +++ b/services/services.go @@ -0,0 +1,93 @@ +package services + +import ( + "encoding/json" + "io" + "net/http" + "os" +) + +var ( + ServiceList []Service + FallbackMap map[string]string +) + +const ( + baseRepoLink = "https://git.sr.ht/~benbusby/farside/blob/main/" + + noCFServicesJSON = "services.json" + fullServicesJSON = "services-full.json" +) + +type Service struct { + Type string `json:"type"` + TestURL string `json:"test_url,omitempty"` + Fallback string `json:"fallback,omimtempty"` + Instances []string `json:"instances"` +} + +func ingestServicesList(servicesBytes []byte) error { + err := json.Unmarshal(servicesBytes, &ServiceList) + return err +} + +func GetServicesFileName() string { + cloudflareEnabled := false + + cfEnabledVar := os.Getenv("FARSIDE_CF_ENABLED") + if len(cfEnabledVar) > 0 && cfEnabledVar == "1" { + cloudflareEnabled = true + } + + serviceJSON := noCFServicesJSON + if cloudflareEnabled { + serviceJSON = fullServicesJSON + } + + return serviceJSON +} + + +func FetchServicesFile(serviceJSON string) ([]byte, error) { + resp, err := http.Get(baseRepoLink + serviceJSON) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + + bodyBytes, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + err = os.WriteFile(serviceJSON, bodyBytes, 0666) + if err != nil { + return nil, err + } + + return bodyBytes, nil +} + +func InitializeServices() error { + serviceJSON := GetServicesFileName() + fileBytes, err := os.ReadFile(serviceJSON) + if err != nil { + fileBytes, err = FetchServicesFile(serviceJSON) + if err != nil { + return err + } + } + + err = ingestServicesList(fileBytes) + if err != nil { + return err + } + + FallbackMap = make(map[string]string) + for _, serviceElement := range ServiceList { + FallbackMap[serviceElement.Type] = serviceElement.Fallback + } + + return nil +} -- cgit v1.2.3