forked from 0x263b/Porygon2
-
Notifications
You must be signed in to change notification settings - Fork 0
/
bot.go
210 lines (183 loc) · 5.27 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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
// Package bot provides a simple to use IRC bot
package bot
import (
"crypto/tls"
"fmt"
"github.com/steveyen/gkvlite"
"github.com/thoj/go-ircevent"
"log"
"math/rand"
"os"
"path/filepath"
"regexp"
"strings"
"time"
)
// Configure must contain the necessary data to connect to an IRC server
type Configure struct {
Server string // IRC server:port. Ex: irc.rizon.net:6697
Channel string // Initial channel to connect. Ex: "#channel"
User string // The IRC username the bot will use
Nick string // The nick the bot will use
Nickserv string // Nickserv password
Password string // Server password
Modes string // User modes. Ex: GRp
UseTLS bool // Should connect using TLS? (yes)
TLSServerName string // Must supply if UseTLS is true
Debug bool // This will log all IRC communication to standad output
Prefix string // Prefix used to identify a command. !hello whould be identified as a command
AltPrefix string
Owner string // Owner of the bot. Used for admin-only commands
API
}
type API struct {
Lastfm string
Giphy string
TranslateClient string
TranslateSecret string
TwitterConsumerKey string
TwitterConsumerSecret string
Weather string
Wolfram string
Youtube string
Geocode string
FootballData string
Dota string
Wordnik string
}
type Directory struct {
Lean string
Quotelog string
}
type ircConnection interface {
Privmsg(target, message string)
GetNick() string
Join(target string)
Part(target string)
}
var (
Conn *irc.Connection
Config *Configure
ChannelNicks = make(map[string][]string)
)
func logChannel(channel, text, senderNick string, action bool) {
t := time.Now()
channel = strings.Replace(channel, "/", "–", -1)
dir, _ := filepath.Abs(filepath.Dir(os.Args[0]))
if _, err := os.Stat(fmt.Sprintf("%s/logs", dir)); os.IsNotExist(err) {
os.MkdirAll(fmt.Sprintf("%s/logs", dir), 0711)
}
mo := fmt.Sprintf("%v", int(t.Month()))
if len(mo) < 2 {
mo = fmt.Sprintf("0%s", mo)
}
f, err := os.OpenFile(fmt.Sprintf("%s/logs/%s.log", dir, channel), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
fmt.Printf("error opening file: %v", err)
}
defer f.Close()
log.SetFlags(0)
log.SetOutput(f)
var line string
if action {
line = fmt.Sprintf("[%s] * %s %s", t.Format(time.RFC3339), senderNick, text)
} else {
line = fmt.Sprintf("[%s] <%s> %s", t.Format(time.RFC3339), senderNick, text)
}
log.Println(line)
return
}
func Out(text string) {
log.Println(text)
}
func onPRIVMSG(e *irc.Event) {
if e.Arguments[0] != Conn.GetNick() {
channel := strings.Replace(e.Arguments[0], "#", "", -1)
logChannel(channel, e.Message(), e.Nick, false)
}
messageReceived(e.Arguments[0], e.Message(), e.Nick, Conn)
}
func onACTION(e *irc.Event) {
if e.Arguments[0] != Conn.GetNick() {
channel := strings.Replace(e.Arguments[0], "#", "", -1)
logChannel(channel, e.Message(), e.Nick, true)
}
// messageReceived(e.Arguments[0], e.Message(), e.Nick, Conn)
}
func getServerName() string {
separatorIndex := strings.LastIndex(Config.Server, ":")
if separatorIndex != -1 {
return Config.Server[:separatorIndex]
} else {
return Config.Server
}
}
func connect() {
Conn = irc.IRC(Config.User, Config.Nick)
Conn.Password = Config.Password
Conn.UseTLS = Config.UseTLS
Conn.TLSConfig = &tls.Config{
ServerName: getServerName(),
InsecureSkipVerify: true,
}
Conn.Version = "Cara → https://github.com/Seventy-Two/Cara"
Conn.VerboseCallbackHandler = Config.Debug
err := Conn.Connect(Config.Server)
if err != nil {
log.Fatal(err)
}
}
func onEndOfMotd(e *irc.Event) {
SetUserKey(Config.Owner, "admin", "true")
Conn.Privmsg("nickserv", "identify "+Config.Nickserv)
Conn.Mode(Config.Nick, Config.Modes)
SetChannelKey(Config.Channel, "auto_join", true)
Channels.VisitItemsAscend([]byte(""), true, func(i *gkvlite.Item) bool {
if GetChannelKey(string(i.Key), "auto_join") {
time.Sleep(2 * time.Second)
Conn.Join(string(i.Key))
}
return true
})
}
func GetNames(channel string) []string {
Conn.SendRaw(fmt.Sprintf("NAMES %v", channel))
time.Sleep(1 * time.Second)
Conn.SendRawf("NAMES %v", channel)
return ChannelNicks[channel]
}
func onNames(e *irc.Event) {
// Strip modes
r, _ := regexp.Compile("([~&@%+])")
s := r.ReplaceAllString(e.Arguments[3], "")
// Combine all responses & remove duplicates
old := ChannelNicks[strings.ToLower(e.Arguments[2])]
nu := strings.Split(s, " ")
uniq := removeDuplicates(append(old, nu...))
ChannelNicks[strings.ToLower(e.Arguments[2])] = uniq
}
func onKick(e *irc.Event) {
if e.Arguments[1] == Config.Nick {
time.Sleep(5 * time.Second)
Conn.Join(e.Arguments[0])
}
}
func ConfigureEvents() {
Conn.AddCallback("376", onEndOfMotd)
Conn.AddCallback("353", onNames)
Conn.AddCallback("KICK", onKick)
Conn.AddCallback("PRIVMSG", onPRIVMSG)
Conn.AddCallback("CTCP_ACTION", onACTION)
}
// Run reads the Config, connect to the specified IRC server and starts the bot.
// The bot will automatically join all the channels specified in the Configuration
func Run(c *Configure) {
initkv()
Config = c
connect()
ConfigureEvents()
Conn.Loop()
}
func init() {
rand.Seed(time.Now().UnixNano())
}