func sd_record_kick(bot *bot.Sp0rkle, line *base.Line) { sd := bot.GetDriver(driverName).(*seenDriver) n, c := line.Storable() kn := db.StorableNick{Nick: line.Args[1]} // SeenNickFromLine doesn't work with the hacks for KICKING and KICKED // First, handle KICKING kr := sd.LastSeenDoing(line.Nick, "KICKING") if kr == nil { kr = seen.SawNick(n, c, "KICKING", line.Args[2]) } else { kr.StorableNick, kr.StorableChan = n, c kr.Timestamp, kr.Text = time.Now(), line.Args[2] } kr.OtherNick = kn _, err := sd.Upsert(kr.Index(), kr) if err != nil { bot.Reply(line, "Failed to store seen data: %v", err) } // Now, handle KICKED ke := sd.LastSeenDoing(line.Args[1], "KICKED") if ke == nil { ke = seen.SawNick(kn, c, "KICKED", line.Args[2]) } else { ke.StorableNick, ke.StorableChan = kn, c ke.Timestamp, ke.Text = time.Now(), line.Args[2] } ke.OtherNick = n _, err = sd.Upsert(ke.Index(), ke) if err != nil { bot.Reply(line, "Failed to store seen data: %v", err) } }
func cd_netmask(bot *bot.Sp0rkle, line *base.Line, ips, nms string) { ip := net.ParseIP(ips) nmip := net.ParseIP(nms) if ip == nil || nmip == nil { bot.ReplyN(line, "either %s or %s couldn't be parsed as an IP", ips, nms) return } // this is a bit of a hack, because using ParseIP to parse // something that's actually a v4 netmask doesn't quite work nm := net.IPMask(nmip.To4()) cidr, bits := nm.Size() if ip.To4() != nil && nm != nil { if bits != 32 { bot.ReplyN(line, "%s doesn't look like a valid IPv4 netmask", nms) return } } else { // IPv6, hopefully nm = net.IPMask(nmip) cidr, bits = nm.Size() if bits != 128 { bot.ReplyN(line, "%s doesn't look like a valid IPv6 netmask", nms) return } } btm, top := cd_netmask_range(ip, nm) bot.ReplyN(line, "%s/%d is in the range %s-%s and has the netmask %s", ip, cidr, btm, top, nmip) }
func cd_calc(bot *bot.Sp0rkle, line *base.Line, maths string) { if num, err := calc.Calc(maths); err == nil { bot.ReplyN(line, "%s = %g", maths, num) } else { bot.ReplyN(line, "%s error while parsing %s", err, maths) } }
func ud_scan(bot *bot.Sp0rkle, ud *urlDriver, line *base.Line) { words := strings.Split(line.Args[1], " ") n, c := line.Storable() for _, w := range words { if util.LooksURLish(w) { if u := ud.GetByUrl(w); u != nil { bot.Reply(line, "%s first mentioned by %s at %s", w, u.Nick, u.Timestamp.Format(time.RFC1123)) continue } u := urls.NewUrl(w, n, c) if len(w) > autoShortenLimit { u.Shortened = ud.Encode(w) } if err := ud.Insert(u); err != nil { bot.ReplyN(line, "Couldn't insert url '%s': %s", w, err) continue } if u.Shortened != "" { bot.Reply(line, "%s's URL shortened as %s%s%s", line.Nick, bot.Prefix, shortenPath, u.Shortened) } ud.lastseen[line.Args[0]] = u.Id } } }
func fd_literal(bot *bot.Sp0rkle, fd *factoidDriver, line *base.Line) { key := ToKey(line.Args[1], false) if count := fd.GetCount(key); count == 0 { bot.ReplyN(line, "I don't know anything about '%s'.", key) return } else if count > 10 && strings.HasPrefix(line.Args[0], "#") { bot.ReplyN(line, "I know too much about '%s', ask me privately.", key) return } // Temporarily turn off flood protection cos we could be spamming a bit. bot.Conn.Flood = true defer func() { bot.Conn.Flood = false }() // Passing an anonymous function to For makes it a little hard to abstract // away in lib/factoids. Fortunately this is something of a one-off. var fact *factoids.Factoid f := func() error { if fact != nil { bot.ReplyN(line, "[%3.0f%%] %s", fact.Chance*100, fact.Value) } return nil } if err := fd.Find(bson.M{"key": key}).For(&fact, f); err != nil { bot.ReplyN(line, "Something literally went wrong: %s", err) } }
func cd_ord(bot *bot.Sp0rkle, line *base.Line, ord string) { r, _ := utf8.DecodeRuneInString(ord) if r == utf8.RuneError { bot.ReplyN(line, "Couldn't parse a utf8 rune from %s", ord) return } bot.ReplyN(line, "ord(%c) is %d, %U, '%s'", r, r, r, cd_utf8repr(r)) }
func cd_netmask_cidr(bot *bot.Sp0rkle, line *base.Line, cidr string) { if _, nm, err := net.ParseCIDR(cidr); err == nil { btm, top := cd_netmask_range(nm.IP, nm.Mask) bot.ReplyN(line, "%s is in the range %s-%s and has the netmask %s", cidr, btm, top, net.IP(nm.Mask)) } else { bot.ReplyN(line, "error parsing ip/cidr %s: %s", cidr, err) } }
func cd_chr(bot *bot.Sp0rkle, line *base.Line, chr string) { // handles decimal, hex, and octal \o/ i, err := strconv.ParseInt(chr, 0, 0) if err != nil { bot.ReplyN(line, "Couldn't parse %s as an integer: %s", chr, err) return } bot.ReplyN(line, "chr(%s) is %c, %U, '%s'", chr, i, i, cd_utf8repr(rune(i))) }
func sd_record_pm(bot *bot.Sp0rkle, line *base.Line) { sd := bot.GetDriver(driverName).(*seenDriver) sn := sd.SeenNickFromLine(line) sn.Text = line.Args[1] _, err := sd.Upsert(sn.Index(), sn) if err != nil { bot.Reply(line, "Failed to store seen data: %v", err) } }
func qd_add(bot *bot.Sp0rkle, qd *quoteDriver, line *base.Line) { n, c := line.Storable() quote := quotes.NewQuote(line.Args[1], n, c) quote.QID = qd.NewQID() if err := qd.Insert(quote); err == nil { bot.ReplyN(line, "Quote added succesfully, id #%d.", quote.QID) } else { bot.ReplyN(line, "Error adding quote: %s.", err) } }
func nd_privmsg(bot *bot.Sp0rkle, line *base.Line) { nd := bot.GetDriver(driverName).(*netDriver) idx := strings.Index(line.Args[1], " ") if !line.Addressed || idx == -1 { return } svc, query := line.Args[1][:idx], line.Args[1][idx+1:] if s, ok := nd.services[svc]; ok { bot.ReplyN(line, "%s", s.LookupResult(query)) } }
func sd_record_chan(bot *bot.Sp0rkle, line *base.Line) { sd := bot.GetDriver(driverName).(*seenDriver) sn := sd.SeenNickFromLine(line) if len(line.Args) > 1 { // If we have a PART message sn.Text = line.Args[1] } _, err := sd.Upsert(sn.Index(), sn) if err != nil { bot.Reply(line, "Failed to store seen data: %v", err) } }
func dd_privmsg(bot *bot.Sp0rkle, line *base.Line) { if !line.Addressed { return } switch { case strings.HasPrefix(line.Args[1], "decide "): opts := splitDelimitedString(line.Args[1][7:]) chosen := strings.TrimSpace(opts[util.RNG.Intn(len(opts))]) bot.ReplyN(line, "%s", chosen) case strings.HasPrefix(line.Args[1], "rand "): bot.ReplyN(line, "%s", randomFloatAsString(line.Args[1][5:], util.RNG)) } }
func fd_delete(bot *bot.Sp0rkle, fd *factoidDriver, line *base.Line) { // Get fresh state on the last seen factoid. ls := fd.Lastseen(line.Args[0], "") if fact := fd.GetById(ls); fact != nil { if err := fd.Remove(bson.M{"_id": ls}); err == nil { bot.ReplyN(line, "I forgot that '%s' was '%s'.", fact.Key, fact.Value) } else { bot.ReplyN(line, "I failed to forget '%s': %s", fact.Key, err) } } else { bot.ReplyN(line, "Whatever that was, I've already forgotten it.") } }
func cd_privmsg(bot *bot.Sp0rkle, line *base.Line) { if !line.Addressed { return } switch { case strings.HasPrefix(line.Args[1], "calc "): cd_calc(bot, line, line.Args[1][5:]) case strings.HasPrefix(line.Args[1], "netmask "): s := strings.Split(line.Args[1], " ") if strings.Index(s[1], "/") != -1 { // Assume we have netmask ip/cidr cd_netmask_cidr(bot, line, s[1]) } else if len(s) == 3 { // Assume we have netmask ip nm cd_netmask(bot, line, s[1], s[2]) } case strings.HasPrefix(line.Args[1], "chr "): cd_chr(bot, line, line.Args[1][4:]) case strings.HasPrefix(line.Args[1], "ord "): cd_ord(bot, line, line.Args[1][4:]) case strings.HasPrefix(line.Args[1], "length "): bot.ReplyN(line, "'%s' is %d characters long", line.Args[1][7:], len(line.Args[1][7:])) case strings.HasPrefix(line.Args[1], "base "): s := strings.Split(line.Args[1], " ") cd_base(bot, line, s[1], s[2]) } }
func ud_privmsg(bot *bot.Sp0rkle, line *base.Line) { ud := bot.GetDriver(driverName).(*urlDriver) // If we're not being addressed directly, short-circuit to scan. if !line.Addressed { ud_scan(bot, ud, line) return } nl := line.Copy() switch { case util.StripAnyPrefix(&nl.Args[1], []string{"url find ", "urlfind ", "url search ", "urlsearch "}): ud_find(bot, ud, nl) case util.HasAnyPrefix(nl.Args[1], []string{"random url", "randurl"}): nl.Args[1] = "" ud_find(bot, ud, nl) case util.StripAnyPrefix(&nl.Args[1], []string{"shorten that", "shorten"}): ud_shorten(bot, ud, nl) case util.StripAnyPrefix(&nl.Args[1], []string{"cache that", "save that", "cache ", "save "}): ud_cache(bot, ud, nl) default: ud_scan(bot, ud, line) } }
func qd_delete(bot *bot.Sp0rkle, qd *quoteDriver, line *base.Line) { qid, err := strconv.Atoi(line.Args[1]) if err != nil { bot.ReplyN(line, "'%s' doesn't look like a quote id.", line.Args[1]) return } if quote := qd.GetByQID(qid); quote != nil { if err := qd.Remove(bson.M{"_id": quote.Id}); err == nil { bot.ReplyN(line, "I forgot quote #%d: %s", qid, quote.Quote) } else { bot.ReplyN(line, "I failed to forget quote #%d: %s", qid, err) } } else { bot.ReplyN(line, "No quote found for id %d", qid) } }
func qd_fetch(bot *bot.Sp0rkle, qd *quoteDriver, line *base.Line) { if qd.rateLimit(line.Nick) { return } qid, err := strconv.Atoi(line.Args[1]) if err != nil { bot.ReplyN(line, "'%s' doesn't look like a quote id.", line.Args[1]) return } quote := qd.GetByQID(qid) if quote != nil { bot.Reply(line, "#%d: %s", quote.QID, quote.Quote) } else { bot.ReplyN(line, "No quote found for id %d", qid) } }
func qd_privmsg(bot *bot.Sp0rkle, line *base.Line) { qd := bot.GetDriver(driverName).(*quoteDriver) if !line.Addressed { return } nl := line.Copy() switch { // Quote add: qadd | quote add | add quote case util.StripAnyPrefix(&nl.Args[1], []string{"quote add ", "qadd ", "add quote "}): qd_add(bot, qd, nl) // Quote delete: qdel | quote del | del quote #?QID case util.StripAnyPrefix(&nl.Args[1], []string{"quote del ", "qdel ", "del quote "}): // Strip optional # before qid if nl.Args[1][0] == '#' { nl.Args[1] = nl.Args[1][1:] } qd_delete(bot, qd, nl) // Quote lookup: quote #QID case util.StripAnyPrefix(&nl.Args[1], []string{"quote #"}): qd_fetch(bot, qd, nl) // Quote lookup: quote | quote regex case strings.ToLower(nl.Args[1]) == "quote": nl.Args[1] = "" fallthrough // This needs to come after the other cases as it will strip just "quote " case util.StripAnyPrefix(&nl.Args[1], []string{"quote "}): qd_lookup(bot, qd, nl) } }
func qd_lookup(bot *bot.Sp0rkle, qd *quoteDriver, line *base.Line) { if qd.rateLimit(line.Nick) { return } quote := qd.GetPseudoRand(line.Args[1]) if quote == nil { bot.ReplyN(line, "No quotes matching '%s' found.", line.Args[1]) return } // TODO(fluffle): qd should take care of updating Accessed internally quote.Accessed++ if err := qd.Update(bson.M{"_id": quote.Id}, quote); err != nil { bot.ReplyN(line, "I failed to update quote #%d: %s", quote.QID, err) } bot.Reply(line, "#%d: %s", quote.QID, quote.Quote) }
func sd_topten(bot *bot.Sp0rkle, sd *seenDriver, line *base.Line) { top := sd.TopTen(line.Args[0]) s := make([]string, 0, 10) for i, n := range top { s = append(s, fmt.Sprintf("#%d: %s - %d", i+1, n.Nick, n.Lines)) } bot.Reply(line, "%s", strings.Join(s, ", ")) }
func fd_info(bot *bot.Sp0rkle, fd *factoidDriver, line *base.Line) { key := ToKey(line.Args[1], false) count := fd.GetCount(key) if count == 0 { bot.ReplyN(line, "I don't know anything about '%s'.", key) return } msgs := make([]string, 0, 10) if key == "" { msgs = append(msgs, fmt.Sprintf("In total, I know %d things.", count)) } else { msgs = append(msgs, fmt.Sprintf("I know %d things about '%s'.", count, key)) } if created := fd.GetLast("created", key); created != nil { c := created.Created msgs = append(msgs, "A factoid") if key != "" { msgs = append(msgs, fmt.Sprintf("for '%s'", key)) } msgs = append(msgs, fmt.Sprintf("was last created on %s by %s,", c.Timestamp.Format(time.ANSIC), c.Nick)) } if modified := fd.GetLast("modified", key); modified != nil { m := modified.Modified msgs = append(msgs, fmt.Sprintf("modified on %s by %s,", m.Timestamp.Format(time.ANSIC), m.Nick)) } if accessed := fd.GetLast("accessed", key); accessed != nil { a := accessed.Accessed msgs = append(msgs, fmt.Sprintf("and accessed on %s by %s.", a.Timestamp.Format(time.ANSIC), a.Nick)) } if info := fd.InfoMR(key); info != nil { if key == "" { msgs = append(msgs, "These factoids have") } else { msgs = append(msgs, fmt.Sprintf("'%s' has", key)) } msgs = append(msgs, fmt.Sprintf( "been modified %d times and accessed %d times.", info.Modified, info.Accessed)) } bot.ReplyN(line, "%s", strings.Join(msgs, " ")) }
func sd_smoke(bot *bot.Sp0rkle, line *base.Line) { if !smokeRx.MatchString(line.Args[1]) { return } sd := bot.GetDriver(driverName).(*seenDriver) sn := sd.LastSeenDoing(line.Nick, "SMOKE") n, c := line.Storable() if sn != nil { bot.ReplyN(line, "You last went for a smoke %s ago...", util.TimeSince(sn.Timestamp)) sn.StorableNick, sn.StorableChan = n, c sn.Timestamp = time.Now() } else { sn = seen.SawNick(n, c, "SMOKE", "") } if _, err := sd.Upsert(sn.Index(), sn); err != nil { bot.Reply(line, "Failed to store smoke data: %v", err) } }
func fd_replace(bot *bot.Sp0rkle, fd *factoidDriver, line *base.Line) { ls := fd.Lastseen(line.Args[0], "") if fact := fd.GetById(ls); fact != nil { // Store the old factoid value old := fact.Value // Replace the value with the new one fact.Value = strings.TrimSpace(line.Args[1]) // Update the Modified field fact.Modify(line.Storable()) // And store the new factoid data if err := fd.Update(bson.M{"_id": ls}, fact); err == nil { bot.ReplyN(line, "'%s' was '%s', now is '%s'.", fact.Key, old, fact.Value) } else { bot.ReplyN(line, "I failed to replace '%s': %s", fact.Key, err) } } else { bot.ReplyN(line, "Whatever that was, I've already forgotten it.") } }
func sd_lines_lookup(bot *bot.Sp0rkle, sd *seenDriver, line *base.Line) { n := line.Nick if idx := strings.Index(line.Args[1], " "); idx != -1 { n = strings.TrimSpace(line.Args[1][idx:]) } sn := sd.LinesFor(n, line.Args[0]) if sn != nil { bot.ReplyN(line, "%s has said %d lines in this channel", sn.Nick, sn.Lines) } }
func sd_record_lines(bot *bot.Sp0rkle, line *base.Line) { sd := bot.GetDriver(driverName).(*seenDriver) sn := sd.LinesFor(line.Nick, line.Args[0]) if sn == nil { n, c := line.Storable() sn = seen.SawNick(n, c, "LINES", "") } sn.Lines++ for _, n := range milestones { if sn.Lines == n { bot.Reply(line, "%s has said %d lines in this channel and"+ "should now shut the f**k up and do something useful", line.Nick, sn.Lines) } } _, err := sd.Upsert(sn.Index(), sn) if err != nil { bot.Reply(line, "Failed to store seen data: %v", err) } }
func sd_record_nick(bot *bot.Sp0rkle, line *base.Line) { sd := bot.GetDriver(driverName).(*seenDriver) sn := sd.SeenNickFromLine(line) sn.Chan = "" sn.Text = line.Args[0] _, err := sd.Upsert(sn.Index(), sn) if err != nil { // We don't have anyone to reply to in this case, so log instead. sd.l.Warn("Failed to store seen data: %v", err) } }
func fd_search(bot *bot.Sp0rkle, fd *factoidDriver, line *base.Line) { keys := fd.GetKeysMatching(line.Args[1]) if keys == nil || len(keys) == 0 { bot.ReplyN(line, "I couldn't think of anything matching '%s'.", line.Args[0]) return } // RESULTS. count := len(keys) if count > 10 { res := strings.Join(keys[:10], "', '") bot.ReplyN(line, "I found %d keys matching '%s', here's the first 10: '%s'.", count, line.Args[1], res) } else { res := strings.Join(keys, "', '") bot.ReplyN(line, "%s: I found %d keys matching '%s', here they are: '%s'.", count, line.Args[1], res) } }
func fd_add(bot *bot.Sp0rkle, fd *factoidDriver, line *base.Line) { var key, val string if strings.Index(line.Args[1], ":=") != -1 { kv := strings.SplitN(line.Args[1], ":=", 2) key = ToKey(kv[0], false) val = strings.TrimSpace(kv[1]) } else { // we use :is to add val = "key is val" kv := strings.SplitN(line.Args[1], ":is", 2) key = ToKey(kv[0], false) val = strings.Join([]string{strings.TrimSpace(kv[0]), "is", strings.TrimSpace(kv[1])}, " ") } n, c := line.Storable() fact := factoids.NewFactoid(key, val, n, c) if err := fd.Insert(fact); err == nil { count := fd.GetCount(key) bot.ReplyN(line, "Woo, I now know %d things about '%s'.", count, key) } else { bot.ReplyN(line, "Error storing factoid: %s.", err) } }
func cd_base(bot *bot.Sp0rkle, line *base.Line, base, num string) { fromto := strings.Split(base, "to") if len(fromto) != 2 { bot.ReplyN(line, "Specify base as: <from base>to<to base>") return } from, errf := strconv.Atoi(fromto[0]) to, errt := strconv.Atoi(fromto[1]) if errf != nil || errt != nil || from < 2 || from > 36 || to < 2 || to > 36 { bot.ReplyN(line, "Either %s or %s is a bad base, must be in range 2-36", fromto[0], fromto[1]) return } i, err := strconv.ParseInt(num, from, 64) if err != nil { bot.ReplyN(line, "Couldn't parse %s as a base %d integer", num, from) return } bot.ReplyN(line, "%s in base %d is %s in base %d", num, from, strconv.FormatInt(i, to), to) }