/* SPDX-License-Identifier: GPL-2.0 * * Copyright (C) 2021 Jason A. Donenfeld. All Rights Reserved. */ package main import ( "bufio" "container/list" "log" "math/rand" "net" "os" "regexp" "strconv" "strings" "sync" "time" "golang.zx2c4.com/irc/hbot" ) var words []string func init() { f, err := os.Open("/usr/share/dict/words") if err != nil { log.Fatalf("Unable to open dictionary: %v", err) } defer f.Close() scanner := bufio.NewScanner(f) matcher := regexp.MustCompile(`^[a-zA-Z0-9_-]{3,}$`) for scanner.Scan() { word := scanner.Text() if !matcher.MatchString(word) { continue } words = append(words, word) } if len(words) == 0 { log.Fatalln("Did not find any words in dictionary") } } func randomNick() string { return words[rand.Intn(len(words))] + words[rand.Intn(len(words))] + strconv.Itoa(rand.Intn(10000)) } type ircWriter struct { mu sync.Mutex bot *hbot.Bot nick string mungedNick string usageElem *list.Element name string } type ircWriters struct { mu sync.Mutex byNick map[string]*ircWriter byUsage list.List server string channel string } func (writers *ircWriters) runWriter(name string, dialer func(network, address string) (net.Conn, error)) { writer := &ircWriter{name: name} startNick := randomNick() logf := func(format string, args ...interface{}) { log.Printf("[DST %s] "+format, append([]interface{}{name}, args...)...) } writer.bot = hbot.NewBot(&hbot.Config{ Host: writers.server, Nick: startNick, User: hbot.CommonBotUserPrefix + startNick, Channels: []string{writers.channel}, Dial: dialer, Logger: hbot.Logger{Verbosef: logf, Errorf: logf}, }) go func() { <-writer.bot.Joined() writers.mu.Lock() defer writers.mu.Unlock() writer.mu.Lock() defer writer.mu.Unlock() writer.usageElem = writers.byUsage.PushBack(writer) }() writer.bot.AddTrigger(hbot.Trigger{ Condition: func(bot *hbot.Bot, m *hbot.Message) bool { return (m.Command == "436" || m.Command == "433") && len(writer.nick) > 0 }, Action: func(bot *hbot.Bot, m *hbot.Message) { logf("Failed with nick %q, trying %q\n", writer.mungedNick, writer.mungedNick+"_") writer.mungedNick += "_" if len(writer.mungedNick) > 16 { usidx := strings.IndexByte(writer.mungedNick, '_') uslen := len(writer.mungedNick[usidx:]) if uslen >= 16 { writer.mungedNick = writer.nick + "-" if len(writer.mungedNick) > 16 { writer.mungedNick = writer.nick[:15] + "-" } } else { writer.mungedNick = writer.mungedNick[:16-uslen] + writer.mungedNick[usidx:] } } bot.SetNick(writer.mungedNick) }, }) writer.bot.Run() writers.mu.Lock() writer.mu.Lock() if writer.usageElem != nil { writers.byUsage.Remove(writer.usageElem) delete(writers.byNick, writer.nick) } writer.mu.Unlock() writers.mu.Unlock() } func (writers *ircWriters) getWriter(nick string) *ircWriter { writers.mu.Lock() if writer, ok := writers.byNick[nick]; ok { writer.mu.Lock() if writer.nick == nick { writers.byUsage.MoveToFront(writer.usageElem) writers.mu.Unlock() if writer.bot.Nick() != writer.mungedNick { log.Printf("[DST %s] Changing nick to %q\n", writer.name, nick) writer.bot.SetNick(writer.mungedNick) } return writer } writer.mu.Unlock() } if writers.byUsage.Len() == 0 { writers.mu.Unlock() return nil } writer := writers.byUsage.Back().Value.(*ircWriter) writer.mu.Lock() delete(writers.byNick, writer.nick) writer.nick = nick writer.mungedNick = nick + "-" if len(writer.mungedNick) > 16 { writer.mungedNick = nick[:15] + "-" } writers.byNick[nick] = writer writers.byUsage.MoveToFront(writer.usageElem) writers.mu.Unlock() log.Printf("[DST %s] Changing nick to %q\n", writer.name, nick) writer.bot.SetNick(writer.mungedNick) return writer } func (writers *ircWriters) queueMessage(from, message string) { writer := writers.getWriter(from) if writer == nil { time.AfterFunc(time.Second*3, func() { writers.queueMessage(from, message) }) return } log.Printf("[DST %s] Queueing message from %q\n", writer.name, from) writer.bot.Msg(writers.channel, message) writer.mu.Unlock() } func newIrcWriterGroup(server, channel string) *ircWriters { return &ircWriters{ mu: sync.Mutex{}, byNick: make(map[string]*ircWriter, 400), server: server, channel: channel, } }