-
Notifications
You must be signed in to change notification settings - Fork 1
/
bot.go
139 lines (113 loc) · 2.79 KB
/
bot.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
package browser
import (
"embed"
"log"
"reflect"
"strings"
"github.com/dineshgowda24/browser/bots"
"gopkg.in/yaml.v2"
)
// Embed the assets/internal directory
//
//go:embed assets/internal
var fs embed.FS
// BotMatcher is the interface for bot matchers
type BotMatcher interface {
Matcher
}
var genericBotName = "Generic Bot"
// Bot is a struct that contains information about the user agent's bot
type Bot struct {
userAgent string // user agent string
exceptions []string // list of user agents that are exceptions
matcher BotMatcher // bot matcher detected
registered bool // indicates if the bot matcher has registered
}
// NewBot returns a new Bot
// It will return an error if the bot exceptions file cannot be read
// It will return an error if the bot exceptions file cannot be unmarshalled
func NewBot(userAgent string) (*Bot, error) {
be, err := fs.ReadFile("assets/internal/bot_exceptions.yml")
if err != nil {
return nil, err
}
e := make([]string, 0)
if err := yaml.Unmarshal(be, &e); err != nil {
return nil, err
}
bot := &Bot{
userAgent: userAgent,
exceptions: e,
}
bot.register()
return bot, nil
}
// register registers the bot matcher
func (b *Bot) register() {
if b.exception() {
return
}
matchers := []Matcher{
bots.NewEmpty(b.userAgent),
bots.NewKnown(b.userAgent, getKnownBots()),
bots.NewKeyword(b.userAgent),
}
for _, m := range matchers {
if m.Match() {
b.matcher = m
break
}
}
b.registered = true
}
// getKnownBots returns the known bots
// It will panic if the known bots file cannot be read
// TODO: return an error
func getKnownBots() map[string]string {
kb, err := fs.ReadFile("assets/internal/known_bots.yml")
if err != nil {
log.Panicln(err)
}
b := make(map[string]string)
if err := yaml.Unmarshal(kb, &b); err != nil {
log.Panicln(err)
}
return b
}
// getMatcher returns the bot matcher detected from the user agent string
// It will register the bot matcher if it has not been registered
func (b *Bot) getMatcher() Matcher {
if !b.registered {
b.register()
}
return b.matcher
}
// exception returns true if the user agent is an exception
func (b *Bot) exception() bool {
for _, e := range b.exceptions {
if strings.Contains(b.userAgent, e) {
return true
}
}
return false
}
// IsBot returns true if the user agent is a bot
func (b *Bot) IsBot() bool {
return b.getMatcher() != nil
}
// Name returns the name of the bot
func (b *Bot) Name() string {
if b.matcher == nil {
return ""
}
return b.matcher.Name()
}
// Why returns the reason why the user agent is a bot
// It will return an empty string if the user agent is not a bot
// It will return the type of the bot matcher detected
func (b *Bot) Why() string {
if b.matcher == nil {
return ""
}
return reflect.TypeOf(b.matcher).String()
}