robots.go

 1package middleware
 2
 3import (
 4	_ "embed"
 5	"slices"
 6	"strings"
 7	"sync/atomic"
 8
 9	"github.com/bits-and-blooms/bloom"
10)
11
12type robots struct {
13	bloom *bloom.BloomFilter
14}
15
16var __robots_filter atomic.Pointer[robots]
17
18//go:generate /bin/bash ./data/download_robots.sh
19//go:embed data/robots.txt
20var robotsTxt string
21
22//go:embed data/whitelist.txt
23var whitelist string
24
25func init() {
26	__robots_filter.Store(&robots{newFilter()})
27}
28
29// IsRobot tests whether the user agent string is a known bot
30func IsRobot(userAgent string) bool {
31	if len(userAgent) == 0 {
32		return false
33	}
34	return __robots_filter.Load().bloom.Test([]byte(userAgent))
35}
36
37func newFilter() *bloom.BloomFilter {
38	ss := getList(robotsTxt)
39	wl := getList(whitelist)
40	b := bloom.NewWithEstimates(uint(len(ss)), 0.01)
41	for _, s := range ss {
42		if !slices.Contains(wl, s) {
43			b.Add([]byte(s))
44		}
45	}
46	return b
47}
48
49func getList(s string) []string {
50	ss := strings.Split(s, "\n")
51	out := make([]string, 0)
52	for _, s := range ss {
53		s = strings.Trim(s, " \r\n")
54		if len(s) > 0 {
55			out = append(out, s)
56		}
57	}
58	return out
59}