// snooze func snooze(ctx *bot.Context) { r, ok := finished[strings.ToLower(ctx.Nick)] if !ok { ctx.ReplyN("No record of an expired reminder for you, sorry!") return } now := time.Now() at := now.Add(30 * time.Minute) if ctx.Text() != "" { at, ok = datetime.Parse(ctx.Text()) if !ok { ctx.ReplyN("Couldn't parse time string '%s'.") return } if at.Before(now) { ctx.ReplyN("You can't snooze reminder into the past, fool.") return } } r.Created = now r.RemindAt = at if _, err := rc.UpsertId(r.Id, r); err != nil { ctx.ReplyN("Error saving reminder: %v", err) return } delete(listed, ctx.Nick) ctx.ReplyN("%s", r.Acknowledge()) Remind(r, ctx) }
func randomCmd(ctx *bot.Context) { if len(ctx.Text()) == 0 { ctx.ReplyN("Be who? Your mum?") return } whom := strings.ToLower(strings.Fields(ctx.Text())[0]) if whom == strings.ToLower(ctx.Me()) { ctx.ReplyN("Ha, you're funny. No, wait. Retarded... I meant retarded.") return } if !shouldMarkov(whom) { if whom == strings.ToLower(ctx.Nick) { ctx.ReplyN("You're not recording markov data. " + "Use 'markov me' to enable collection.") } else { ctx.ReplyN("Not recording markov data for %s.", ctx.Text()) } return } source := mc.Source("user:"******"%s would say: %s", ctx.Text(), out) } else { ctx.ReplyN("markov error: %v", err) } }
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) } }
// Factoid add: 'key := value' or 'key :is value' func insert(ctx *bot.Context) { if !ctx.Addressed || !util.IsFactoidAddition(ctx.Text()) { return } var key, val string if strings.Index(ctx.Text(), ":=") != -1 { kv := strings.SplitN(ctx.Text(), ":=", 2) key = ToKey(kv[0], false) val = strings.TrimSpace(kv[1]) } else { // we use :is to add val = "key is val" kv := strings.SplitN(ctx.Text(), ":is", 2) key = ToKey(kv[0], false) val = strings.Join([]string{strings.TrimSpace(kv[0]), "is", strings.TrimSpace(kv[1])}, " ") } n, c := ctx.Storable() fact := factoids.NewFactoid(key, val, n, c) // The "randomwoot" factoid contains random positive phrases for success. joy := "Woo" if rand := fc.GetPseudoRand("randomwoot"); rand != nil { joy = rand.Value } if err := fc.Insert(fact); err == nil { count := fc.GetCount(key) LastSeen(ctx.Target(), fact.Id) ctx.ReplyN("%s, I now know %d things about '%s'.", joy, count, key) } else { ctx.ReplyN("Error storing factoid: %s.", err) } }
func urlScan(ctx *bot.Context) { words := strings.Split(ctx.Text(), " ") n, c := ctx.Storable() for _, w := range words { if util.LooksURLish(w) { if u := uc.GetByUrl(w); u != nil { if u.Nick != bot.Nick(ctx.Nick) && time.Since(u.Timestamp) > 2*time.Hour { ctx.Reply("that URL first mentioned by %s %s ago", u.Nick, util.TimeSince(u.Timestamp)) } continue } u := urls.NewUrl(w, n, c) if len(w) > autoShortenLimit && ctx.Public() { u.Shortened = Encode(w) } if err := uc.Insert(u); err != nil { ctx.ReplyN("Couldn't insert url '%s': %s", w, err) continue } if u.Shortened != "" { ctx.Reply("%s's URL shortened as %s%s%s", ctx.Nick, bot.HttpHost(), shortenPath, u.Shortened) } lastseen[ctx.Target()] = u.Id } } }
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 mcSet(ctx *bot.Context) { kv := strings.Fields(ctx.Text()) if len(kv) < 2 { ctx.ReplyN("I need a key and a value.") return } switch kv[0] { case mcServer: mcConf.String(mcServer, kv[1]) case mcChan: if !strings.HasPrefix(kv[1], "#") { ctx.ReplyN("Channel '%s' doesn't start with #.", kv[1]) return } mcConf.String(mcChan, kv[1]) case mcFreq: freq, err := strconv.Atoi(kv[1]) if err != nil { ctx.ReplyN("Couldn't convert '%s' to an integer.", kv[1]) return } mcConf.Int(mcFreq, freq) default: ctx.ReplyN("Valid keys are: %s, %s, %s", mcServer, mcFreq, mcChan) return } ctx.ReplyN("Set %s to '%s'", kv[0], kv[1]) }
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 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 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) } }
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 calculate(ctx *bot.Context) { nick := strings.ToLower(ctx.Nick) maths := ctx.Text() tm := calc.TokenMap{} if res, ok := results[nick]; ok { tm["result"] = res } if num, err := calc.Calc(maths, tm); err == nil { ctx.ReplyN("%s = %g", maths, num) tm[nick] = num } else { ctx.ReplyN("%s error while parsing %s", err, maths) } }
func chr(ctx *bot.Context) { chr := strings.ToLower(ctx.Text()) if strings.HasPrefix(chr, "u+") { // Allow "unicode" syntax by translating it to 0x... chr = "0x" + chr[2:] } // handles decimal, hex, and octal \o/ i, err := strconv.ParseInt(chr, 0, 0) if err != nil { ctx.ReplyN("Couldn't parse %s as an integer: %s", chr, err) return } ctx.ReplyN("chr(%s) is %c, %U, '%s'", chr, i, i, utf8repr(rune(i))) }
// tell func tell(ctx *bot.Context) { // s == <target> <stuff> txt := ctx.Text() idx := strings.Index(txt, " ") if idx == -1 { ctx.ReplyN("Tell who what?") return } tell := txt[idx+1:] n, c := ctx.Storable() t := bot.Nick(txt[:idx]) if t.Lower() == strings.ToLower(ctx.Nick) || t.Lower() == "me" { ctx.ReplyN("You're a dick. Oh, wait, that wasn't *quite* it...") return } r := reminders.NewTell(tell, t, n, c) if err := rc.Insert(r); err != nil { ctx.ReplyN("Error saving tell: %v", err) return } if s := pc.GetByNick(txt[:idx]); s.CanPush() { push.Push(s, fmt.Sprintf("%s in %s asked me to tell you:", ctx.Nick, ctx.Target()), tell) } // Any previously-generated list of reminders is now obsolete. delete(listed, ctx.Nick) ctx.ReplyN("%s", r.Acknowledge()) }
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 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 lookup(ctx *bot.Context) { // Only perform extra prefix removal if we weren't addressed directly key := ToKey(ctx.Text(), !ctx.Addressed) var fact *factoids.Factoid if fact = fc.GetPseudoRand(key); fact == nil && ctx.Cmd == client.ACTION { // Support sp0rkle's habit of stripping off it's own nick // but only for actions, not privmsgs. if strings.HasSuffix(key, ctx.Me()) { key = strings.TrimSpace(key[:len(key)-len(ctx.Me())]) fact = fc.GetPseudoRand(key) } } if fact == nil { return } // Chance is used to limit the rate of factoid replies for things // people say a lot, like smilies, or 'lol', or 'i love the peen'. chance := fact.Chance if key == "" { // This is doing a "random" lookup, triggered by someone typing in // something entirely composed of the chars stripped by ToKey(). // To avoid making this too spammy, forcibly limit the chance to 40%. chance = 0.4 } if rand.Float64() < chance { // Store this as the last seen factoid LastSeen(ctx.Target(), fact.Id) // Update the Accessed field // TODO(fluffle): fd should take care of updating Accessed internally fact.Access(ctx.Storable()) // And store the new factoid data if err := fc.Update(bson.M{"_id": fact.Id}, fact); err != nil { ctx.ReplyN("I failed to update '%s' (%s): %s ", fact.Key, fact.Id, err) } recurse(fact, map[string]bool{key: true}) switch fact.Type { case factoids.F_ACTION: ctx.Do("%s", fact.Value) default: ctx.Reply("%s", fact.Value) } } }
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) }
// Factoid info: 'fact info key' => some information about key func info(ctx *bot.Context) { key := ToKey(ctx.Text(), false) count := fc.GetCount(key) if count == 0 { ctx.ReplyN("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 := fc.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,", datetime.Format(c.Timestamp), c.Nick)) } if modified := fc.GetLast("modified", key); modified != nil { m := modified.Modified msgs = append(msgs, fmt.Sprintf("modified on %s by %s,", datetime.Format(m.Timestamp), m.Nick)) } if accessed := fc.GetLast("accessed", key); accessed != nil { a := accessed.Accessed msgs = append(msgs, fmt.Sprintf("and accessed on %s by %s.", datetime.Format(a.Timestamp), a.Nick)) } if info := fc.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)) } ctx.ReplyN("%s", strings.Join(msgs, " ")) }
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 insult(ctx *bot.Context) { source := mc.Source("tag:insult") whom, lc := ctx.Text(), strings.ToLower(ctx.Text()) if lc == strings.ToLower(ctx.Me()) || lc == "yourself" { ctx.ReplyN("Ha, you're funny. No, wait. Retarded... I meant retarded.") return } if lc == "me" { whom = ctx.Nick } if out, err := chain.Sentence(source); err == nil { if len(whom) > 0 { ctx.Reply("%s: %s", whom, out) } else { ctx.Reply("%s", out) } } else { ctx.ReplyN("markov error: %v", err) } }
func pushConfirm(ctx *bot.Context) { pin := strings.Fields(ctx.Text())[0] s := pc.GetByNick(ctx.Nick, false) switch { case s == nil: ctx.ReplyN("No authentication state found.") return case s.Done: ctx.ReplyN("Pushes already enabled.") return case pin != s.Pin: ctx.ReplyN("Incorrect pin.") return } s.Done = true if err := pc.SetState(s); err != nil { ctx.ReplyN("Error setting push state: %v", err) return } ctx.ReplyN("Pushes enabled! Yay!") }
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 pushAddAlias(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("Alias %q already exists.", alias) return } if a := pc.GetByNick(alias); a != nil { ctx.ReplyN("Alias %q already exists for nick %s.", alias, a.Nick) return } s.AddAlias(alias) if err := pc.SetState(s); err != nil { ctx.ReplyN("Error setting push state: %v", err) return } ctx.ReplyN("Added alias %q to your push state.", alias) }
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) }
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) }
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 del(ctx *bot.Context) { txt := ctx.Text() // Strip optional # before qid if txt[0] == '#' { txt = txt[1:] } qid, err := strconv.Atoi(txt) if err != nil { ctx.ReplyN("'%s' doesn't look like a quote id.", ctx.Text()) return } if quote := qc.GetByQID(qid); quote != nil { if err := qc.RemoveId(quote.Id); err == nil { ctx.ReplyN("I forgot quote #%d: %s", qid, quote.Quote) } else { ctx.ReplyN("I failed to forget quote #%d: %s", qid, err) } } else { ctx.ReplyN("No quote found for id %d", qid) } }
func pushDisable(ctx *bot.Context) { // Do not search by aliases here: it allows someone to change nick // to a known alias and then disable pushes for that user. s := pc.GetByNick(ctx.Nick, false) if s == nil { ctx.ReplyN("Pushes not enabled.") return } if err := pc.DelState(s); err != nil { ctx.ReplyN("Error deleting push state: %v", err) return } ctx.ReplyN("Ok, pushes disabled.") }