func tellCheck(ctx *bot.Context) { nick := ctx.Nick if ctx.Cmd == client.NICK { // We want the destination nick, not the source. nick = ctx.Target() } r := rc.TellsFor(nick) for i := range r { if ctx.Cmd == client.NICK { if r[i].Chan != "" { ctx.Privmsg(string(r[i].Chan), nick+": "+r[i].Reply()) } ctx.Reply("%s", r[i].Reply()) } else { ctx.Privmsg(ctx.Nick, r[i].Reply()) if r[i].Chan != "" { ctx.ReplyN("%s", r[i].Reply()) } } rc.RemoveId(r[i].Id) } if len(r) > 0 { delete(listed, ctx.Nick) } }
func githubCreateIssue(ctx *bot.Context) { s := strings.SplitN(ctx.Text(), ". ", 2) if s[0] == "" { ctx.ReplyN("I'm not going to create an empty issue.") return } req := &github.IssueRequest{ Title: &s[0], Labels: &[]string{ "from:IRC", "nick:" + ctx.Nick, "chan:" + ctx.Target(), }, } if len(s) == 2 { req.Body = &s[1] } issue, _, err := gh.Issues.Create(githubUser, githubRepo, req) if err != nil { ctx.ReplyN("Error creating issue: %v", err) return } ctx.ReplyN("Issue #%d created at %s/%d", *issue.Number, githubIssuesURL, *issue.Number) }
func Remind(r *reminders.Reminder, ctx *bot.Context) { delta := r.RemindAt.Sub(time.Now()) if delta < 0 { return } c := make(chan struct{}) running[r.Id] = c go func() { select { case <-time.After(delta): ctx.Privmsg(string(r.Chan), r.Reply()) // TODO(fluffle): Tie this into state tracking properly. ctx.Privmsg(string(r.Target), r.Reply()) // This is used in snooze to reinstate reminders. finished[strings.ToLower(string(r.Target))] = r if pc != nil { if s := pc.GetByNick(string(r.Target)); s.CanPush() { push.Push(s, "Reminder from sp0rkle!", r.Reply()) } } Forget(r.Id, false) case <-c: return } }() }
func convertBase(ctx *bot.Context) { s := strings.Split(ctx.Text(), " ") fromto := strings.Split(s[0], "to") if len(fromto) != 2 { ctx.ReplyN("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 { ctx.ReplyN("Either %s or %s is a bad base, must be in range 2-36", fromto[0], fromto[1]) return } i, err := strconv.ParseInt(s[1], from, 64) if err != nil { ctx.ReplyN("Couldn't parse %s as a base %d integer", s[1], from) return } ctx.ReplyN("%s in base %d is %s in base %d", s[1], from, strconv.FormatInt(i, to), to) }
func recordKick(ctx *bot.Context) { n, c := ctx.Storable() kn := bot.Nick(ctx.Text()) // seenNickFromLine doesn't work with the hacks for KICKING and KICKED // First, handle KICKING kr := sc.LastSeenDoing(ctx.Nick, "KICKING") if kr == nil { kr = seen.SawNick(n, c, "KICKING", ctx.Args[2]) } else { kr.Nick, kr.Chan = n, c kr.Timestamp, kr.Text = time.Now(), ctx.Args[2] } kr.OtherNick = kn _, err := sc.Upsert(kr.Id(), kr) if err != nil { ctx.Reply("Failed to store seen data: %v", err) } // Now, handle KICKED ke := sc.LastSeenDoing(ctx.Text(), "KICKED") if ke == nil { ke = seen.SawNick(kn, c, "KICKED", ctx.Args[2]) } else { ke.Nick, ke.Chan = kn, c ke.Timestamp, ke.Text = time.Now(), ctx.Args[2] } ke.OtherNick = n _, err = sc.Upsert(ke.Id(), ke) if err != nil { ctx.Reply("Failed to store seen data: %v", err) } }
func topten(ctx *bot.Context) { top := sc.TopTen(ctx.Target()) s := make([]string, 0, 10) for i, n := range top { s = append(s, fmt.Sprintf("#%d: %s - %d", i+1, n.Nick, n.Lines)) } ctx.Reply("%s", strings.Join(s, ", ")) }
func recordNick(ctx *bot.Context) { sn := seenNickFromLine(ctx) sn.Chan = "" sn.Text = ctx.Target() if _, err := sc.Upsert(sn.Id(), sn); err != nil { // We don't have anyone to reply to in this case, so log instead. logging.Warn("Failed to store seen data: %v", err) } }
func disableMarkov(ctx *bot.Context) { key := strings.ToLower(ctx.Nick) conf.Ns(markovNs).Delete(key) if err := mc.ClearTag("user:"******"Failed to clear tag: %s", err) return } ctx.ReplyN("Sure, bro, I'll stop.") }
func recordJoin(ctx *bot.Context) { sn := seenNickFromLine(ctx) if len(ctx.Args) > 1 { // If we have a PART message sn.Text = ctx.Text() } if _, err := sc.Upsert(sn.Id(), sn); err != nil { ctx.Reply("Failed to store seen data: %v", err) } }
// Split this out so we can inject a deterministic time for testing. func id_replacer(val string, ctx *bot.Context, ts time.Time) string { val = strings.Replace(val, "$nick", ctx.Nick, -1) val = strings.Replace(val, "$chan", ctx.Target(), -1) val = strings.Replace(val, "$username", ctx.Ident, -1) val = strings.Replace(val, "$user", ctx.Ident, -1) val = strings.Replace(val, "$host", ctx.Host, -1) val = strings.Replace(val, "$date", datetime.Format(ts), -1) val = strings.Replace(val, "$time", datetime.Format(ts, "15:04:05"), -1) return val }
// Look up or create a "seen" entry for the line. // Explicitly don't handle updating line.Text or line.OtherNick func seenNickFromLine(ctx *bot.Context) *seen.Nick { sn := sc.LastSeenDoing(ctx.Nick, ctx.Cmd) n, c := ctx.Storable() if sn == nil { sn = seen.SawNick(n, c, ctx.Cmd, "") } else { sn.Nick, sn.Chan = n, c sn.Timestamp = time.Now() } return sn }
func netmask(ctx *bot.Context) { s := strings.Split(ctx.Text(), " ") if len(s) == 1 && strings.Index(s[0], "/") != -1 { // Assume we have netmask ip/cidr ctx.ReplyN("%s", parseCIDR(s[0])) } else if len(s) == 2 { // Assume we have netmask ip nm ctx.ReplyN("%s", parseMask(s[0], s[1])) } else { ctx.ReplyN("bad netmask args: %s", ctx.Text()) } }
func karmaCmd(ctx *bot.Context) { if k := kc.KarmaFor(ctx.Text()); k != nil { ctx.ReplyN("%s", k) } else { ctx.ReplyN("No karma found for '%s'", ctx.Text()) } }
func fetch(ctx *bot.Context) { if RateLimit(ctx.Nick) { return } qid, err := strconv.Atoi(ctx.Text()) if err != nil { ctx.ReplyN("'%s' doesn't look like a quote id.", ctx.Text()) return } quote := qc.GetByQID(qid) if quote != nil { ctx.Reply("#%d: %s", quote.QID, quote.Quote) } else { ctx.ReplyN("No quote found for id %d", qid) } }
func recordStats(ctx *bot.Context) { ns := sc.StatsFor(ctx.Nick, ctx.Target()) if ns == nil { n, c := ctx.Storable() ns = stats.NewStat(n, c) } ns.Update(ctx.Text()) if ns.Lines%10000 == 0 { ctx.Reply("%s has said %d lines in this channel and "+ "should now shut the f**k up and do something useful", ctx.Nick, ns.Lines) } if _, err := sc.Upsert(ns.Id(), ns); err != nil { ctx.Reply("Failed to store stats data: %v", err) } }
func lookup(ctx *bot.Context) { if RateLimit(ctx.Nick) { return } quote := qc.GetPseudoRand(ctx.Text()) if quote == nil { ctx.ReplyN("No quotes matching '%s' found.", ctx.Text()) return } // TODO(fluffle): qd should take care of updating Accessed internally quote.Accessed++ if err := qc.Update(bson.M{"_id": quote.Id}, quote); err != nil { ctx.ReplyN("I failed to update quote #%d: %s", quote.QID, err) } ctx.Reply("#%d: %s", quote.QID, quote.Quote) }
func add(ctx *bot.Context) { n, c := ctx.Storable() quote := quotes.NewQuote(ctx.Text(), n, c) quote.QID = qc.NewQID() if err := qc.Insert(quote); err == nil { ctx.ReplyN("Quote added succesfully, id #%d.", quote.QID) } else { ctx.ReplyN("Error adding quote: %s.", err) } }
func statsCmd(ctx *bot.Context) { n := ctx.Nick if len(ctx.Text()) > 0 { n = ctx.Text() } ns := sc.StatsFor(n, ctx.Target()) if ns != nil { ctx.ReplyN("%s", ns) } }
// remind del func del(ctx *bot.Context) { list, ok := listed[ctx.Nick] if !ok { ctx.ReplyN("Please use 'remind list' first, " + "to be sure of what you're deleting.") return } idx, err := strconv.Atoi(ctx.Text()) if err != nil || idx > len(list) || idx <= 0 { ctx.ReplyN("Invalid reminder index '%s'", ctx.Text()) return } idx-- Forget(list[idx], true) delete(listed, ctx.Nick) ctx.ReplyN("I'll forget that one, then...") }
func pushDelAlias(ctx *bot.Context) { alias := strings.Fields(ctx.Text())[0] s := pc.GetByNick(ctx.Nick, false) if s == nil || !s.CanPush() { ctx.ReplyN("Pushes not enabled.") return } if !s.HasAlias(alias) { ctx.ReplyN("%q is not one of your aliases.", alias) return } s.DelAlias(alias) if err := pc.SetState(s); err != nil { ctx.ReplyN("Error setting push state: %v", err) return } ctx.ReplyN("Deleted alias %q from your push state.", alias) }
// remind list func list(ctx *bot.Context) { r := rc.RemindersFor(ctx.Nick) c := len(r) if c == 0 { ctx.ReplyN("You have no reminders set.") return } if c > 5 && ctx.Public() { ctx.ReplyN("You've got lots of reminders, ask me privately.") return } // Save an ordered list of ObjectIds for easy reminder deletion ctx.ReplyN("You have %d reminders set:", c) list := make([]bson.ObjectId, c) for i := range r { ctx.Reply("%d: %s", i+1, r[i].List(ctx.Nick)) list[i] = r[i].Id } listed[ctx.Nick] = list }
func learn(ctx *bot.Context) { s := strings.SplitN(ctx.Text(), " ", 2) if len(s) != 2 { ctx.ReplyN("I can't learn from you, you're an idiot.") return } // Prepending "tag:" prevents people from learning as "user:foo". mc.AddSentence(s[1], "tag:"+s[0]) if ctx.Public() { // Allow large-scale learning via privmsg by not replying there. ctx.ReplyN("Ta. You're a fount of knowledge, you are.") } }
func githubWatcher(ctx *bot.Context) { // Watch #sp0rklf for IRC messages about issues coming from github. if ctx.Nick != "fluffle\\sp0rkle" || ctx.Target() != "#sp0rklf" || !strings.Contains(ctx.Text(), "issue #") { return } text := util.RemoveColours(ctx.Text()) // srsly github why colours :( l := &util.Lexer{Input: text} l.Find(' ') text = text[l.Pos()+1:] l.Find('#') l.Next() issue := int(l.Number()) labels, _, err := gh.Issues.ListLabelsByIssue( githubUser, githubRepo, issue, &github.ListOptions{}) if err != nil { logging.Error("Error getting labels for issue %d: %v", issue, err) return } for _, l := range labels { kv := strings.Split(*l.Name, ":") if len(kv) == 2 && kv[0] == "nick" { logging.Debug("Recording tell for %s about issue %d.", kv[1], issue) r := reminders.NewTell("that "+text, bot.Nick(kv[1]), "github", "") if err := rc.Insert(r); err != nil { logging.Error("Error inserting github tell: %v", err) } } } }
func urbanDictionary(ctx *bot.Context) { entry, ok, err := cache.fetch(strings.ToLower(ctx.Text())) if err != nil { ctx.ReplyN("ud request failed: %s", err) return } cached, r := "", entry.result if ok { cached = fmt.Sprintf(", result cached at %s", datetime.Format(entry.stamp)) } if r.Total == 0 || r.Type == "no_results" { ctx.ReplyN("%s isn't defined yet%s.", ctx.Text(), cached) return } // Cycle through all the definitions on repeated calls for the same term r.Pages = (r.Pages + 1) % r.Total def := r.List[r.Pages] ctx.Reply("[%d/%d] %s (%d up, %d down%s)", r.Pages+1, r.Total, strings.Replace(def.Definition, "\r\n", " ", -1), def.Upvotes, def.Downvotes, cached) }
// Factoid delete: 'forget|delete that' => deletes lastSeen[chan] func forget(ctx *bot.Context) { // Get fresh state on the last seen factoid. ls := LastSeen(ctx.Target(), "") if fact := fc.GetById(ls); fact != nil { if err := fc.Remove(bson.M{"_id": ls}); err == nil { ctx.ReplyN("I forgot that '%s' was '%s'.", fact.Key, fact.Value) } else { ctx.ReplyN("I failed to forget '%s': %s", fact.Key, err) } } else { ctx.ReplyN("Whatever that was, I've already forgotten it.") } }
func ord(ctx *bot.Context) { ord := ctx.Text() r, _ := utf8.DecodeRuneInString(ord) if r == utf8.RuneError { ctx.ReplyN("Couldn't parse a utf8 rune from %s", ord) return } ctx.ReplyN("ord(%c) is %d, %U, '%s'", r, r, r, utf8repr(r)) }
func recordPrivmsg(ctx *bot.Context) { if !ctx.Public() { return } sn := seenNickFromLine(ctx) sn.Text = ctx.Text() if _, err := sc.Upsert(sn.Id(), sn); err != nil { ctx.Reply("Failed to store seen data: %v", err) } }
func smoke(ctx *bot.Context) { if !smokeRx.MatchString(ctx.Text()) { return } sn := sc.LastSeenDoing(ctx.Nick, "SMOKE") n, c := ctx.Storable() if sn != nil { ctx.ReplyN("You last went for a smoke %s ago...", util.TimeSince(sn.Timestamp)) sn.Nick, sn.Chan = n, c sn.Timestamp = time.Now() } else { sn = seen.SawNick(n, c, "SMOKE", "") } if _, err := sc.Upsert(sn.Id(), sn); err != nil { ctx.Reply("Failed to store smoke data: %v", err) } }
func date(ctx *bot.Context) { tstr, zone := ctx.Text(), "" if idx := strings.Index(tstr, "in "); idx != -1 { tstr, zone = tstr[:idx], strings.TrimSpace(tstr[idx+3:]) } tm, ok := time.Now(), true if tstr != "" { if tm, ok = datetime.Parse(tstr); !ok { ctx.ReplyN("Couldn't parse time string '%s'.", tstr) return } } if loc := datetime.Zone(zone); zone != "" && loc != nil { tm = tm.In(loc) ctx.ReplyN("%s", tm.Format(datetime.TimeFormat)) } else { ctx.ReplyN("%s", datetime.Format(tm)) } }
func recordMarkov(ctx *bot.Context) { whom := strings.ToLower(ctx.Nick) if !ctx.Addressed && ctx.Public() && shouldMarkov(whom) { // Only markov lines that are public, not addressed to us, // and from markov-enabled nicks switch ctx.Cmd { case client.PRIVMSG: mc.AddSentence(ctx.Text(), "user:"******"user:"+whom) } } }