1
0
mirror of https://github.com/TehPeGaSuS/xmasbot.git synced 2026-07-09 04:45:43 +02:00
Files
xmasbot/main.go
T
TehPeGaSuS 0a04a71fd3 Add Merry Christmas IRC party bot
This commit introduces a new IRC party bot written in Go, which announces Christmas messages in different time zones. It includes various command-line options for configuration, such as channels, nicknames, and server settings.
2025-12-20 19:46:07 +01:00

319 lines
7.6 KiB
Go

// Merry Christmas IRC party bot
package main
import (
"crypto/tls"
"flag"
"fmt"
"net"
"os"
"regexp"
"time"
"github.com/badoux/checkmail"
"github.com/fatih/color"
"github.com/TehPeGaSuS/xmasbot/nyb"
log "gopkg.in/inconshreveable/log15.v2"
"gopkg.in/yaml.v3"
)
const usage = `
Merry Christmas IRC party bot
Announces merry christmas as they happen in each timezone
CMD Options:
[mandatory]
-channels comma separated list of channels eg. "#test, #test2"
channel key can be specifed after ":" e.g #channelname:channelkey
-nick irc nick
-email nominatim email
[optional]
-password irc server password
-saslnick sasl username
-saslpass sasl password
-server irc server (default: irc.libera.chat:6697)
-prefix command prefix (default: !)
-nossl disable ssl for irc
-nocheck disable ssl verification (e.g. self signed ssl)
-nominatim nominatim server (default: http://nominatim.openstreetmap.org)
-nolimit disable flood kick protection
-colors enable irc colors
-bind bind to host address
-debug debug irc traffic
-yaml yaml config file
`
const SET_NOMINATIM_SERVER = "https://nominatim.openstreetmap.org"
const SET_LIBERA_SERVER = "irc.libera.chat:6697"
const SET_PREFIX = "!"
func main() {
var channels nyb.Channels
// Mandatory
flag.Var(&channels, "channels", "comma separated list of channels")
nick := flag.String("nick", "", "irc nick")
email := flag.String("email", "", "nominatim email")
// Optional
password := flag.String("password", "", "irc server password")
saslNick := flag.String("saslnick", "", "sasl username")
saslPass := flag.String("saslpass", "", "sasl password")
server := flag.String("server", SET_LIBERA_SERVER, "irc server")
prefix := flag.String("prefix", SET_PREFIX, "command prefix")
nossl := flag.Bool("nossl", false, "disable ssl for irc")
nocheck := flag.Bool("nocheck", false, "disable ssl verification (e.g. self signed ssl)")
nominatim := flag.String("nominatim", SET_NOMINATIM_SERVER, "nominatim server")
nolimit := flag.Bool("nolimit", false, "disable limit bot replies.")
colors := flag.Bool("colors", false, "enable irc colors")
debug := flag.Bool("debug", false, "debug irc traffic")
bind := flag.String("bind", "", "bind to host address")
configYAML := flag.String("yaml", "", "use yaml settings file")
green := color.New(color.FgGreen)
flag.Usage = func() {
green.Fprint(os.Stderr, usage)
}
flag.Parse()
c := config{
{
Nick: *nick,
Channels: channels,
Server: *server,
NoSSL: *nossl,
NoCheck: *nocheck,
Password: *password,
SaslNick: *saslNick,
SaslPass: *saslPass,
Prefix: *prefix,
Email: *email,
Nominatim: *nominatim,
NoLimit: *nolimit,
Colors: *colors,
Debug: *debug,
Bind: *bind,
},
}
red := color.New(color.FgHiRed)
if *configYAML != "" {
data, err := os.ReadFile(*configYAML)
if err != nil {
red.Fprintln(os.Stderr, "yaml file: ", err)
os.Exit(1)
}
c = config{}
err = yaml.Unmarshal(data, &c)
if err != nil {
red.Fprintln(os.Stderr, "yaml file: ", err)
os.Exit(1)
}
for i := range c {
if c[i].Nominatim == "" {
c[i].Nominatim = SET_NOMINATIM_SERVER
}
if c[i].Server == "" {
c[i].Server = SET_LIBERA_SERVER
}
if c[i].Prefix == "" {
c[i].Prefix = SET_PREFIX
}
}
}
err := check(c)
if err != nil {
red.Fprintln(os.Stderr, err)
if *configYAML == "" {
flag.Usage()
}
os.Exit(1)
}
var bots []*nyb.Settings
for _, c := range c {
var err error
var customTLSDial nyb.TLSDialFunc
var customDial nyb.DialFunc
if c.NoSSL && c.Bind != "" {
customDial, err = func() (nyb.DialFunc, error) {
if c.Bind == "" {
return nil, nil
}
localAddr, err := net.ResolveIPAddr("ip", c.Bind)
if err != nil {
return nil, err
}
localTCPAddr := net.TCPAddr{
IP: localAddr.IP,
}
remoteAddr, err := net.ResolveTCPAddr("tcp", c.Server)
if err != nil {
return nil, err
}
dialer := func(network string, addr string) (net.Conn, error) {
return net.DialTCP(network, &localTCPAddr, remoteAddr)
}
return dialer, nil
}()
if err != nil {
fmt.Fprintln(os.Stderr, "BINDHOST:", err)
os.Exit(1)
}
}
if !c.NoSSL && c.Bind != "" {
customTLSDial, err = func() (nyb.TLSDialFunc, error) {
if c.Bind == "" {
return nil, nil
}
localAddr, err := net.ResolveIPAddr("ip", c.Bind)
if err != nil {
return nil, err
}
localTCPAddr := net.TCPAddr{
IP: localAddr.IP,
}
dialer := &net.Dialer{
LocalAddr: &localTCPAddr,
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}
tlsSettings := &tls.Config{}
if c.NoCheck {
tlsSettings.InsecureSkipVerify = true
}
tlsdialer := func(network string, addr string, tlsConf *tls.Config) (*tls.Conn, error) {
return tls.DialWithDialer(dialer, network, addr, tlsSettings)
}
return tlsdialer, nil
}()
if err != nil {
fmt.Fprintln(os.Stderr, "BINDHOST:", err)
os.Exit(1)
}
}
if !c.NoSSL && c.NoCheck && c.Bind == "" {
customTLSDial = func() nyb.TLSDialFunc {
dialer := &net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}
skipverify := &tls.Config{InsecureSkipVerify: true}
tlsdialer := func(network string, addr string, tlsConf *tls.Config) (*tls.Conn, error) {
return tls.DialWithDialer(dialer, network, addr, skipverify)
}
return tlsdialer
}()
}
bots = append(bots,
nyb.New(
&nyb.Settings{
Nick: c.Nick,
Channels: c.Channels,
Server: c.Server,
SSL: !c.NoSSL,
Password: c.Password,
SaslNick: c.SaslNick,
SaslPass: c.SaslPass,
Prefix: c.Prefix,
Email: c.Email,
Nominatim: c.Nominatim,
Limit: !c.NoLimit,
Colors: c.Colors,
Dial: customDial,
TLSDial: customTLSDial,
},
),
)
}
for i, bot := range bots {
if c[i].Debug {
bot.LogLvl(log.LvlDebug)
} else {
bot.LogLvl(log.LvlInfo)
}
go bot.Start()
}
select {}
}
type config []struct {
Nick string
Channels []string
Server string
NoSSL bool
NoCheck bool
Password string
SaslNick string
SaslPass string
Prefix string
Email string
Nominatim string
NoLimit bool
Colors bool
Debug bool
Bind string
}
func check(c config) error {
if len(c) == 0 {
return fmt.Errorf("empty or misconfigured yaml")
}
for _, c := range c {
// Check mandatory inputs
if len(c.Channels) == 0 {
return fmt.Errorf("error: no channels defined")
}
channelReg := regexp.MustCompile(`^([#&][^\x07\x2C\s]{0,200})$`)
for _, ch := range c.Channels {
if !channelReg.MatchString(ch) {
return fmt.Errorf("error: invalid channel name: %s", ch)
}
}
if c.Nick == "" {
return fmt.Errorf("error: no nick defined")
}
if len(c.Nick) > 16 {
return fmt.Errorf("error: nick too long")
}
if c.Email == "" {
return fmt.Errorf("error: no email provided")
}
if err := checkmail.ValidateFormat(c.Email); err != nil {
return fmt.Errorf("error: invalid email address")
}
// Check optional inputs
if c.Server == "" {
return fmt.Errorf("error: no irc server defined")
}
serverReg := regexp.MustCompile(`^\S+:\d+$`)
if !serverReg.MatchString(c.Server) {
return fmt.Errorf("error: invalid irc server address")
}
if c.Prefix == "" {
return fmt.Errorf("error: no command prefix defined")
}
prefixReg := regexp.MustCompile(`^\W+$`)
if !prefixReg.MatchString(c.Prefix) {
return fmt.Errorf("error: prefix must be non-alphanumeric")
}
if c.Nominatim == "" {
return fmt.Errorf("error: no nominatim server provided")
}
}
return nil
}