forked from fipar/minibot
/
ircbot.go
283 lines (259 loc) · 7.57 KB
/
ircbot.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
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
/*
Implements a basic IRC bot using github.com/thoj/go-ircevent
*/
package main
import (
"code.google.com/p/gosqlite/sqlite"
"flag"
"fmt"
"github.com/thoj/go-ircevent"
"strconv"
"strings"
"time"
)
type seenRecord struct {
message string
when time.Time
}
var (
con *irc.Connection
db *sqlite.Conn
host, nick, user, channel, database string
verbose bool
seenNicks = map[string]seenRecord{}
startTime = time.Now()
whoisReplies = make(chan string, 1)
)
func main() {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
con.Privmsg(channel, "An error ocurred and should have been logged")
}
}()
for {
func() {
flag.StringVar(&host, "host", "irc.freenode.net:6667", "The IRC host:port to connect to. Defaults to irc.freenode.net:6667")
flag.StringVar(&nick, "nick", "minibot", "The IRC nick to use. Defaults to minibot")
flag.StringVar(&user, "user", "minibot", "The IRC user to use. Defaults to minibot")
flag.StringVar(&channel, "channel", "#minibot", "The IRC channel to join. Defaults to #minibot")
flag.StringVar(&database, "database", "minibot.db", "The sqlite database file. Defaults to minibot.db")
flag.BoolVar(&verbose, "verbose", false, "Be verbose")
flag.Parse()
var err error
debug("connecting to irc")
con = irc.IRC(nick, user)
debug("opening sqlite database")
db, err = sqlite.Open(database)
if err != nil {
panic("An error occurred while opening the database: " + err.Error())
}
defer db.Close()
debug("if needed, initializing sqlite database")
err = db.Exec("create table if not exists messages (sender text, destination text, moment text, message text, primary key (sender, destination, moment))")
if err != nil {
panic("Could not initialize database: " + err.Error())
}
debug("connecting to irc server")
err = con.Connect(host)
if err != nil {
panic("An error occurred while connecting to irc: " + err.Error())
}
con.AddCallback("001", func(event *irc.Event) { con.Join(channel) })
con.AddCallback("311", func(event *irc.Event) { whoisReplies <- event.Message })
con.AddCallback("PRIVMSG", respond)
con.AddCallback("NOTICE", func(event *irc.Event) { debug(event.Message) })
con.AddCallback("NICK", messages)
debug("starting main loop")
con.Loop()
}()
}
}
// helper function to print debug messages
func debug(message string) {
if verbose {
fmt.Println(message)
}
}
// Function to respond to PRIVMSG events.
func respond(event *irc.Event) {
see(event.Nick, event.Message)
messages(event)
op := strings.Split(event.Message, " ")[0]
if len(op) > 1 && op[0] == '!' {
switch op {
case "!ping":
{
reply(event, "pong")
}
case "!whoami":
{
reply(event, "You are "+event.Nick)
}
case "!help":
{
printHelp(event)
}
case "!countdown":
{
countdown(event)
}
case "!seen":
{
seen(event)
}
case "!uptime":
{
reply(event, "uptime: "+time.Since(startTime).String())
}
case "!message":
{
message(event)
}
case "!beer":
{
reply(event, "!beer has been deprecated")
}
case "!slap":
{
reply(event, "!slap has been deprecated")
}
case "!error":
{
n := 0
fmt.Print("%d",1 / n)
}
case "!opme":
{
con.SendRaw("MODE " + channel + " +o " + event.Nick)
}
default:
{
reply(event, "Unknown command. Try !help")
}
}
}
}
// returns true if nick is online (that happens if whois <nick> gets back to the bot in under a second)
func isOnline(nick string) bool {
con.SendRaw("WHOIS " + nick)
select {
case reply := <-whoisReplies:
return strings.Split(reply, " ")[0] == nick
case <-time.After(1 * time.Second):
return false
}
return false
}
// checks if a user has messages waiting
func messages(event *irc.Event) {
nick := event.Nick
if event.Code == "NICK" {
nick = event.Message
}
st, err := db.Prepare("select sender, moment, message from messages where destination = '" + nick + "' ")
if err != nil {
return
}
err = st.Exec()
if err != nil {
return
}
for st.Next() {
sender := ""
moment := ""
message := ""
st.Scan(&sender, &moment, &message)
reply(event, "On "+moment+", "+sender+" said : "+message)
db.Exec("delete from messages where destination = '" + nick + "' and sender = '" + sender + "' and moment = '" + moment + "'")
}
}
// saves a message for a user
func message(event *irc.Event) {
args := strings.Split(event.Message, " ")
if len(args) < 3 {
reply(event, "I need a destination and a message to do this. ")
return
}
destination := args[1]
if isOnline(destination) {
reply(event, "Seems "+destination+" is online, why don't you just talk to him directly?")
return
}
message := ""
for i := 2; i < len(args); i++ {
message += args[i] + " "
}
err := db.Exec("insert into messages (sender,destination,message,moment) values ('" + event.Nick + "','" + destination + "','" + message + "','" + time.Now().Format("2006-01-02 15:04:05 MST") + "')")
if err != nil {
reply(event, "An error occurred while saving the message: "+err.Error())
return
}
reply(event, "The message has been saved")
}
// sets/updates the last seen time and message for a nick
func see(nick string, message string) {
seenNicks[nick] = seenRecord{message, time.Now()}
}
// checks if a nick has been seen
func seen(event *irc.Event) {
args := strings.Split(event.Message, " ")
if len(args) < 2 {
reply(event, "You didn't specify a nick")
return
}
nick := args[1]
last, present := seenNicks[nick]
if present == false {
reply(event, "I have not seen "+nick+" since I've started ("+startTime.Format("2006-01-02 15:04 MST")+")")
} else {
reply(event, "I last saw "+nick+" on "+last.when.Format("2006-01-02 15:04 MST")+" and he said "+last.message)
}
}
// helper function to send a reply to a channel or user
func reply(event *irc.Event, message string) {
target := channel
if len(event.Arguments) > 0 {
target = event.Arguments[0]
}
con.Privmsg(target, message)
}
// sleeps for the specified amount of minutes, and then alerts the nick that invoked it, optionally printing a message
// TODO: I am not explicitly (AFAIK) creating a new goroutine here, yet this does not block the bot. I need to understand why that is the case.
func countdown(event *irc.Event) {
args := strings.Split(event.Message, " ")
if len(args) < 2 {
reply(event, "I need at least an amount in minutes to wait, and optionally a message to give you when the wait is over. ")
return
}
dur, err := strconv.Atoi(args[1])
if err != nil {
reply(event, "My first argument has to be a number. I will be the number of minutes I will sleep before alerting you.")
return
}
reply(event, "I will alert you in "+args[1]+" minutes")
msg := "You asked me to alert you " + args[1] + " minutes ago"
if len(args) > 2 {
msg = ""
for i := 2; i < len(args); i++ {
msg += args[i] + " "
}
msg += " (" + args[1] + " minutes ago)"
}
time.Sleep(time.Duration(dur) * time.Minute)
reply(event, msg)
}
// prints a line for each command, with a brief description.
func printHelp(event *irc.Event) {
helpItems := [...]string{
"!ping : replies pong",
"!whoami : replies 'you are <nick>'",
"!countdown <i> [s]: sleeps i minutes and alerts you, optionally printing s",
"!seen <nick>: tells you the last time <nick> was seen by the bot",
"!message <nick> <text>: saves <text> for when <nick> is online",
"!uptime: prints the bot's uptime",
"!help : prints basic help"}
for _, v := range helpItems {
reply(event, v)
}
}