// 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) }
// 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 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 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 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 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 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 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 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 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 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) } }
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 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 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 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 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) } } }
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.") } }
// 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 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 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))) }
// Factoid chance: 'chance of that is' => sets chance of lastSeen[chan] func chance(ctx *bot.Context) { str := ctx.Text() var chance float64 if strings.HasSuffix(str, "%") { // Handle 'chance of that is \d+%' if i, err := strconv.Atoi(str[:len(str)-1]); err != nil { ctx.ReplyN("'%s' didn't look like a % chance to me.", str) return } else { chance = float64(i) / 100 } } else { // Assume the chance is a floating point number. if c, err := strconv.ParseFloat(str, 64); err != nil { ctx.ReplyN("'%s' didn't look like a chance to me.", str) return } else { chance = c } } // Make sure the chance we've parsed lies in (0.0,1.0] if chance > 1.0 || chance <= 0.0 { ctx.ReplyN("'%s' was outside possible chance ranges.", str) return } // Retrieve last seen ObjectId, replace with "" ls := LastSeen(ctx.Target(), "") // ok, we're good to update the chance. if fact := fc.GetById(ls); fact != nil { // Store the old chance, update with the new old := fact.Chance fact.Chance = chance // Update the Modified field fact.Modify(ctx.Storable()) // And store the new factoid data if err := fc.Update(bson.M{"_id": ls}, fact); err == nil { ctx.ReplyN("'%s' was at %.0f%% chance, now is at %.0f%%.", fact.Key, old*100, chance*100) } else { ctx.ReplyN("I failed to replace '%s': %s", fact.Key, err) } } else { ctx.ReplyN("Whatever that was, I've already forgotten it.") } }
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) { 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 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) { // 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) } } }
// 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, " ")) }
// 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) }
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)) } }