func ItsAlive(conn *irc.Conn, line *irc.Line) { if line.Args[0] != conn.Me().Nick { // no private query, ignore return } msg := line.Args[1] if !strings.HasPrefix(msg, "msg ") { //~ log.Printf("unknown prefix for: %s", msg) // only accept valid commands return } if line.Nick != frankconf.Master { // only answer to master log.Printf("only answering to master %s, but was %s", frankconf.Master, line.Nick) return } cmd := strings.SplitN(msg, " ", 3) channel := cmd[1] msg = cmd[2] log.Printf("master command: posting “%s” to %s", msg, channel) conn.Privmsg(channel, msg) }
func postTitle(conn *irc.Conn, line *irc.Line, title string, prefix string) { tgt := line.Args[0] secondsAgo := cacheGetSecondsToLastPost(title) if secondsAgo <= noRepostWithinSeconds { log.Printf("Skipping, because posted %d seconds ago (“%s”)", secondsAgo, title) return } if frankconf.Verbose { log.Printf("Title was last posted: %#v (“%s”)", secondsAgo, title) } log.Printf("nick=%s, target=%s, title=%s", line.Nick, tgt, title) // if target is our current nick, it was a private message. // Answer the users in this case. if tgt == conn.Me().Nick { tgt = line.Nick } if prefix == "" { prefix = "Link Info" } else { prefix = clean(prefix) } title = clean(title) // the IRC spec states that notice should be used instead of msg // and that bots should not react to notice at all. However, no // real world bot adheres to this. Furthermore, people who can’t // configure their client to not highlight them on notices will // complain. conn.Privmsg(tgt, "["+prefix+"] "+title) }
func onInvite(irc *client.Conn, line *client.Line) { who, channel := line.Args[0], line.Args[1] log.Println(line.Nick, "invited bot to", channel) if who == irc.Me().Nick { // some IRCds only allow operators to INVITE, and on registered channels normally only identified users are operators // check anyway, since there are some corner cases where that doesn't happen if checkIdentified(irc, line.Nick) { log.Println("Accepting invite to", channel) irc.Join(channel) } else { irc.Notice(line.Nick, "you must be identified to invite") log.Println("Ignoring invite, user is not identified") } } }
func Help(conn *irc.Conn, line *irc.Line) { if line.Args[0] != conn.Me().Nick { // no private query, ignore return } if line.Args[1] != "help" && line.Args[1] != "!help" { // no help request, ignore return } last := lastHelps[line.Nick] if time.Since(last).Minutes() <= 1 { log.Printf("User %s tried spamming for help, not answering (last request @ %v)", line.Nick, last) return } lastHelps[line.Nick] = time.Now() conn.Privmsg(line.Nick, "1. Test your IRC client’s highlighting:") conn.Privmsg(line.Nick, " – /msg frank high") conn.Privmsg(line.Nick, " – /msg frank high custom_text") conn.Privmsg(line.Nick, " – /msg frank highpub custom_text") conn.Privmsg(line.Nick, "“high” sends you a private message, “highpub” posts to #test.") conn.Privmsg(line.Nick, "Your nick will be used unless custom_text is defined. Delay is always 5 seconds.") conn.Privmsg(line.Nick, " ") conn.Privmsg(line.Nick, "2. I won’t spoiler URLs if you add “no spoiler” to your message") conn.Privmsg(line.Nick, " ") conn.Privmsg(line.Nick, "3. There’s a karma system. You can’t vote on yourself.") conn.Privmsg(line.Nick, " – thing++ # optional comment") conn.Privmsg(line.Nick, " – thing-- # thing may be alphanumerical, Unicode is supported") conn.Privmsg(line.Nick, " – karma for thing // karma thing // karma thing?") conn.Privmsg(line.Nick, " ") conn.Privmsg(line.Nick, "4. I’ll answer to !raum in certain channels.") conn.Privmsg(line.Nick, " ") conn.Privmsg(line.Nick, "If you need more details, please look at my source:") conn.Privmsg(line.Nick, "https://github.com/breunigs/frank") }
func Highlight(conn *irc.Conn, line *irc.Line) { defer func() { if r := recover(); r != nil { log.Printf("MEGA-WTF:pkg: %v", r) } }() if line.Args[0] != conn.Me().Nick { // no private query, ignore return } msg := line.Args[1] if !strings.HasPrefix(msg, "high") { // no highlight request, ignore return } log.Printf("received highlighting request from %s\n", line.Nick) var highlight string if customTextRegex.MatchString(msg) { match := customTextRegex.FindStringSubmatch(msg) highlight = match[1] } else { highlight = line.Nick } // allow for 100ms round trip time to highlight on time time.Sleep(4900 * time.Millisecond) if strings.HasPrefix(msg, "highpub") { log.Printf("highlighting %s publicly for: %s\n", line.Nick, highlight) conn.Privmsg("#test", "highlight test: "+highlight) } else { log.Printf("highlighting %s privately for: %s\n", line.Nick, highlight) conn.Privmsg(line.Nick, highlight) } }
// answers a user with the current karma for a given thing func answer(conn *irc.Conn, line *irc.Line) { tgt := line.Args[0] msg := line.Args[1] if !karmaAnswerRegex.MatchString(msg) { return } match := karmaAnswerRegex.FindStringSubmatch(msg) if len(match) != 2 || match[1] == "" { log.Printf("WTF: karma answer regex somehow failed and produced invalid results") return } score := strconv.Itoa(data[strings.ToLower(match[1])]) // if we were the target, it was a private message. Answer user instead if tgt == conn.Me().Nick { tgt = line.Nick } conn.Privmsg(tgt, "[Karma] "+match[1]+": "+score) }
func reportAllNowPlaying(irc *client.Conn, asker, channel string) { if !(strings.HasPrefix(channel, "#") || strings.HasPrefix(channel, "&")) { log.Println("User", asker, "asked What's Playing...... via PM") irc.Privmsg(channel, fmt.Sprintf("%s: this only works on channels", asker)) return } log.Println("User", asker, "requested What's Playing on channel", channel) if !checkIdentified(irc, asker) { r := fmt.Sprintf("%s: you must be identified with NickServ to use this command", asker) log.Println(r) irc.Privmsg(channel, r) return } if _, ok := whoChannel[channel]; ok { log.Println("Channel", channel, "is already executing a What's Playing request") return } whoChannel[channel] = make(chan bool, 1) go irc.Who(channel) for _ = range whoChannel[channel] { // wait until channel is closed } delete(whoChannel, channel) reportChan := make(chan bool) totalReport := len(whoResult[channel]) - 1 msg := fmt.Sprintf("Reporting now playing for %d nicks in channel %s", totalReport, channel) log.Println(msg) irc.Notice(asker, msg) for _, nick := range whoResult[channel] { if nick != irc.Me().Nick { n := nick go func() { rateLimit <- true reportChan <- reportNowPlaying(irc, channel, asker, n, true) <-rateLimit }() } } delete(whoResult, channel) okCount, totalCount := 0, 0 for r := range reportChan { if r { okCount++ } if totalCount++; totalCount == totalReport { break } } close(reportChan) msg = fmt.Sprintf("Reported for %d of %d nicks", okCount, totalCount) log.Println(msg) irc.Notice(asker, msg) return }
func (plugin *Plugin) OnJoin(conn *client.Conn, line *client.Line) { logging.Info("vpnbot.Plugin: %v joined %v", line.Src, line.Target()) if lastCheck, ok := plugin.lastCheckNicks[line.Nick]; ok && time.Now().Sub(lastCheck) < 15*time.Minute { // There is a check in progress or another one has been done earlier logging.Debug("vpnbot.Plugin: Not checking %v, last check was %v", line.Nick, humanize.Time(plugin.lastCheckNicks[line.Nick])) return } logging.Debug("vpnbot.Plugin: Checking %v...", line.Nick) plugin.lastCheckNicks[line.Nick] = time.Now() // Is this me? if line.Nick == conn.Me().Nick { logging.Debug("vpnbot.Plugin: %v is actually me, skipping.", line.Nick) return } // Nickname == Ident? (9 chars cut) if !strings.HasPrefix(nonWordCharsRegex.ReplaceAllString(line.Nick, ""), strings.TrimLeft(line.Ident, "~")) { logging.Debug("vpnbot.Plugin: %v's nick doesn't match the ident, skipping.", line.Nick) return } // Hostname is masked RDNS vhost/IP? // TODO - Use regex to avoid banning due to similar vhosts if !maskedAddrRegex.MatchString(line.Host) { // Detected custom vHost logging.Debug("vpnbot.Plugin: %v has a custom vhost, skipping.", line.Nick) return } go func() { botNick := line.Nick nobotActivityChan := make(chan string) defer plugin.bot.HandleFunc("privmsg", func(conn *client.Conn, line *client.Line) { if line.Nick == botNick { nobotActivityChan <- "User sent a message" } }).Remove() defer plugin.bot.HandleFunc("noticed", func(conn *client.Conn, line *client.Line) { if line.Nick == botNick { nobotActivityChan <- "User sent a notice" } }).Remove() defer plugin.bot.HandleFunc("part", func(conn *client.Conn, line *client.Line) { if line.Nick == botNick { nobotActivityChan <- "User left" } }).Remove() defer plugin.bot.HandleFunc("part", func(conn *client.Conn, line *client.Line) { if line.Nick == botNick { } if len(line.Args) > 0 { switch line.Args[0] { case "Excess flood": // If this was an excess flood, consider it spam that should // be good to ban anyways nobotActivityChan <- "Excess flood, banning early" banmask := fmt.Sprintf("%v!%v@%v", "*", "*", line.Host) // TODO - Ramp up/down the duration with increasing/decreasing activity of the bots plugin.banGlobal(plugin.generateBan(line.Nick, banmask, "Instant excess flood", 2*24*time.Hour)) default: nobotActivityChan <- "User quit normally" } } }).Remove() // Give nobotActivityChan some time to prove this is not a bot select { case reason := <-nobotActivityChan: logging.Info("vpnbot.Plugin: %v, skipping.", reason) return case <-time.After(1 * time.Second): } // Get WHOIS info info, err := plugin.whois.WhoIs(line.Nick) if err != nil && err != whois.ErrNoSuchNick { logging.Warn("vpnbot.Plugin: Can't get WHOIS info for %v, skipping: %v", line.Nick, err.Error()) return } // Not an oper? if info.IsOperator { logging.Debug("vpnbot.Plugin: %v is operator, skipping.", line.Nick) return } // Not away if info.IsAway { logging.Debug("vpnbot.Plugin: %v is away, skipping.", line.Nick) return } // Realname == Nickname? if info.Realname != line.Nick { logging.Debug( "vpnbot.Plugin: %v's nick doesn't match the realname, skipping.", line.Nick) return } // Count of channels at least 48 if len(info.Channels) < 48 { logging.Debug( "vpnbot.Plugin: %v is not in a high amount of channels, skipping.", line.Nick) return } // Not halfop, op, aop or owner in any channel for _, prefix := range info.Channels { if prefix == '%' || prefix == '@' || prefix == '&' || prefix == '~' { logging.Debug( "vpnbot.Plugin: %v is opped in a channel, skipping.", line.Nick) return } } // Give nobotActivityChan some time to prove this is not a bot select { case reason := <-nobotActivityChan: logging.Info("vpnbot.Plugin: %v, skipping.", reason) return case <-time.After(250 * time.Millisecond): } // More expensive tests below... // Make sure we deal with an unregistered client status := plugin.nickserv.Status(line.Nick)[line.Nick] if status.Error != nil { logging.Warn("vpnbot.Plugin: Can't get auth status for %v, skipping: %v", line.Nick, status.Error) return } if status.Level >= nickserv.Status_IdentifiedViaPassword { logging.Debug("vpnbot.Plugin: %v is identified, skipping.", line.Nick) return } // Give nobotActivityChan some time to prove this is not a bot select { case reason := <-nobotActivityChan: logging.Info("vpnbot.Plugin: %v, skipping.", reason) return case <-time.After(250 * time.Millisecond): } // This is a bot we need to ban! banmask := fmt.Sprintf("%v!%v@%v", "*", "*", line.Host) // TODO - Ramp up/down the duration with increasing/decreasing activity of the bots plugin.banGlobal(plugin.generateBan(line.Nick, banmask, "Known pattern of ban-evading/logging bot", 2*24*time.Hour)) }() }