aboutsummaryrefslogtreecommitdiffstats
path: root/cmd/irc-simple-responder/main.go
blob: cd86b9f8c72709455b1c88c6bdac918d726936a0 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
/* 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)
	}
}