/* SPDX-License-Identifier: GPL-2.0 * * Copyright (C) 2021 Jason A. Donenfeld. All Rights Reserved. */ package main import ( "bufio" "flag" "fmt" "log" "net" "os" "regexp" "strings" "sync" "time" "golang.zx2c4.com/irc/hbot" ) func main() { channelsArg := flag.String("channels", "", "channels to join, separated by commas") serverArg := flag.String("server", "", "server and port") nickArg := flag.String("nick", "", "nickname") passwordArg := flag.String("password-file", "", "optional file with password") messageArg := flag.String("message", "", "message with which to respond") flag.Parse() if matched, _ := regexp.MatchString(`^(#[a-zA-Z0-9_-]+,)*(#[a-zA-Z0-9_-]+)$`, *channelsArg); !matched { fmt.Fprintln(os.Stderr, "Invalid channels") flag.Usage() os.Exit(1) } if _, _, err := net.SplitHostPort(*serverArg); err != nil { fmt.Fprintln(os.Stderr, "Invalid server") flag.Usage() os.Exit(1) } if matched, _ := regexp.MatchString(`^[a-zA-Z0-9\[\]_-]{1,16}$`, *nickArg); !matched { fmt.Fprintln(os.Stderr, "Invalid nick") flag.Usage() os.Exit(1) } password := "" if len(*passwordArg) > 0 { f, err := os.Open(*passwordArg) if err != nil { fmt.Fprintf(os.Stderr, "Unable to open password file: %v\n", err) os.Exit(1) } password, err = bufio.NewReader(f).ReadString('\n') f.Close() if err != nil { fmt.Fprintf(os.Stderr, "Unable to read password file: %v\n", err) os.Exit(1) } if len(password) > 0 && password[len(password)-1] == '\n' { password = password[:len(password)-1] } } if len(*messageArg) == 0 { fmt.Fprintln(os.Stderr, "Missing message") flag.Usage() os.Exit(1) } const messengerTimeout = time.Minute * 10 messengers := make(map[string]time.Time, 1024) var messengersMu sync.Mutex go func() { for range time.Tick(messengerTimeout / 20) { messengersMu.Lock() for nick, last := range messengers { if time.Since(last) >= messengerTimeout { delete(messengers, nick) } } messengersMu.Unlock() } }() bot := hbot.NewBot(&hbot.Config{ Host: *serverArg, Nick: *nickArg, User: hbot.CommonBotUserPrefix + *nickArg, Channels: strings.Split(*channelsArg, ","), Logger: hbot.Logger{Verbosef: log.Printf, Errorf: log.Printf}, Password: password, }) bot.AddTrigger(hbot.Trigger{ Condition: func(b *hbot.Bot, m *hbot.Message) bool { if m.Command != "PRIVMSG" || strings.HasPrefix(m.Prefix.User, hbot.CommonBotUserPrefix) || strings.HasPrefix(m.Prefix.User, "~"+hbot.CommonBotUserPrefix) { return false } nick := strings.ToLower(m.Prefix.Name) messengersMu.Lock() defer messengersMu.Unlock() if last, ok := messengers[nick]; ok && time.Since(last) < messengerTimeout { return false } if len(messengers) > 1024*1024*1024 { return false } messengers[nick] = time.Now() return true }, Action: func(b *hbot.Bot, m *hbot.Message) { message := *messageArg target := m.Prefix.Name if strings.Contains(m.Param(0), "#") { target = m.Param(0) if target[0] == '@' || target[0] == '+' { target = target[1:] } message = m.Prefix.Name + ": " + message } log.Printf("Responding to %q in %q", m.Prefix.String(), target) b.Msg(target, message) }, }) for { bot.Run() time.Sleep(time.Second * 5) } }