commit 955bedb8f94fb9214aa41fe88fb457757455bea0 Author: null31 Date: Fri Aug 30 14:22:01 2024 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c012e54 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +irc_bot +*.mmdb +*.json diff --git a/config.go b/config.go new file mode 100644 index 0000000..2bea57b --- /dev/null +++ b/config.go @@ -0,0 +1,33 @@ +package main + +import ( + "encoding/json" + "os" +) + +type Config struct { + IRCServer string `json:"irc_server"` + IRCPort int `json:"irc_port"` + SSL bool `json:"ssl"` + Nickname string `json:"nickname"` + Channels []string `json:"channels"` + GeoIPDatabase string `json:"geoip_database"` + GeoIPASN string `json:"geoip_asn"` +} + +func LoadConfig(filePath string) (*Config, error) { + file, err := os.Open(filePath) + if err != nil { + return nil, err + } + defer file.Close() + + config := &Config{} + decoder := json.NewDecoder(file) + err = decoder.Decode(config) + if err != nil { + return nil, err + } + + return config, nil +} diff --git a/config.json.example b/config.json.example new file mode 100644 index 0000000..105a3f9 --- /dev/null +++ b/config.json.example @@ -0,0 +1,9 @@ +{ + "irc_server": "irc.network.example", + "irc_port": 6697, + "ssl": true, + "nickname": "Some_nick", + "channels": ["#channel", "#channel2"], + "geoip_database": "/app/geolite2-city.mmdb", + "geoip_asn": "/app/geolite2-asn.mmdb" +} diff --git a/dockerfile b/dockerfile new file mode 100644 index 0000000..2a778b6 --- /dev/null +++ b/dockerfile @@ -0,0 +1,18 @@ +FROM debian:12-slim AS builder + +ADD url /app/ + +WORKDIR /app + +RUN apt update && apt upgrade -y && apt install musl musl-dev && \ + git clone https://github.com/richfelker/musl-cross-make.git && \ + cd musl-cross-make && make && make install && cd .. && \ + CGO_ENABLED=1 CC=musl-gcc GOOS=linux GOARCH=amd64 go build -a -ldflags '-extldflags "-static"' -o irc_bot + +FROM alpine:3.20 + +WORKDIR /app + +COPY --from=builder /app/irc_bot /app/ + +CMD ["irc_bot"] \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..99aae23 --- /dev/null +++ b/go.mod @@ -0,0 +1,12 @@ +module irc_bot + +go 1.23.0 + +require ( + github.com/oschwald/geoip2-golang v1.11.0 // indirect + github.com/oschwald/maxminddb-golang v1.13.0 // indirect + github.com/thoj/go-ircevent v0.0.0-20210723090443-73e444401d64 // indirect + golang.org/x/net v0.0.0-20210614182718-04defd469f4e // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/text v0.3.6 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..73b738a --- /dev/null +++ b/go.sum @@ -0,0 +1,16 @@ +github.com/oschwald/geoip2-golang v1.11.0 h1:hNENhCn1Uyzhf9PTmquXENiWS6AlxAEnBII6r8krA3w= +github.com/oschwald/geoip2-golang v1.11.0/go.mod h1:P9zG+54KPEFOliZ29i7SeYZ/GM6tfEL+rgSn03hYuUo= +github.com/oschwald/maxminddb-golang v1.13.0 h1:R8xBorY71s84yO06NgTmQvqvTvlS/bnYZrrWX1MElnU= +github.com/oschwald/maxminddb-golang v1.13.0/go.mod h1:BU0z8BfFVhi1LQaonTwwGQlsHUEu9pWNdMfmq4ztm0o= +github.com/thoj/go-ircevent v0.0.0-20210723090443-73e444401d64 h1:l/T7dYuJEQZOwVOpjIXr1180aM9PZL/d1MnMVIxefX4= +github.com/thoj/go-ircevent v0.0.0-20210723090443-73e444401d64/go.mod h1:Q1NAJOuRdQCqN/VIWdnaaEhV8LpeO2rtlBP7/iDJNII= +golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q= +golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/main.go b/main.go new file mode 100644 index 0000000..faa12da --- /dev/null +++ b/main.go @@ -0,0 +1,153 @@ +package main + +import ( + "fmt" + "log" + "net" + "strings" + "crypto/tls" + + "github.com/oschwald/geoip2-golang" + "github.com/thoj/go-ircevent" +) + +func main() { + config, err := LoadConfig("config.json") + if err != nil { + log.Fatalf("Error loading config: %v", err) + } + + irccon := irc.IRC(config.Nickname, config.Nickname) + irccon.VerboseCallbackHandler = true + irccon.Debug = true + irccon.UseTLS = config.SSL + irccon.TLSConfig = &tls.Config{InsecureSkipVerify: true} + + err = irccon.Connect(fmt.Sprintf("%s:%d", config.IRCServer, config.IRCPort)) + if err != nil { + log.Fatalf("Failed to connect to IRC server: %v", err) + } + + irccon.AddCallback("001", func(e *irc.Event) { + for _, channel := range config.Channels { + irccon.Join(channel) + } + }) + + irccon.AddCallback("PRIVMSG", func(e *irc.Event) { + if len(e.Arguments) < 2 { + return + } + + message := e.Arguments[1] + parts := strings.Fields(message) + if len(parts) == 0 { + return + } + + cmd := strings.TrimPrefix(parts[0], "!") + if commandFunc, ok := commands[cmd]; ok { + commandFunc(irccon, e, parts) + } + }) + + irccon.Loop() +} + +type CommandFunc func(irccon *irc.Connection, e *irc.Event, args []string) + +var commands = map[string]CommandFunc{ + "geoip": handleGeoIPCommand, + // add more commands here +} + +func handleGeoIPCommand(irccon *irc.Connection, e *irc.Event, args []string) { + if len(args) < 2 { + irccon.Privmsg(e.Arguments[0], "Usage: !geoip ") + return + } + + ipOrDomain := args[1] + + config, err := LoadConfig("config.json") + if err != nil { + log.Fatalf("Error loading config: %v", err) + } + + cityDbPath := config.GeoIPDatabase // Update with actual path + asnDbPath := config.GeoIPASN // Update with actual path + + ip := net.ParseIP(ipOrDomain) + if ip == nil { + resolvedIP, err := net.LookupIP(ipOrDomain) + if err != nil || len(resolvedIP) == 0 { + irccon.Privmsg(e.Arguments[0], fmt.Sprintf("Invalid IP address or domain: %s", ipOrDomain)) + return + } + ip = resolvedIP[0] + } + + var ( + city = "N/A" + subdivision = "N/A" + country = "N/A" + latitude = "N/A" + longitude = "N/A" + asn = "N/A" + isp = "N/A" + ) + + // Lookup City Information + if cityDb, err := geoip2.Open(cityDbPath); err == nil { + defer cityDb.Close() + + if cityRecord, err := cityDb.City(ip); err == nil { + if name := cityRecord.City.Names["en"]; name != "" { + city = name + } + if len(cityRecord.Subdivisions) > 0 { + if name := cityRecord.Subdivisions[0].Names["en"]; name != "" { + subdivision = name + } + } + if name := cityRecord.Country.Names["en"]; name != "" { + country = name + } + if lat := cityRecord.Location.Latitude; lat != 0 { + latitude = fmt.Sprintf("%.6f", lat) + } + if lon := cityRecord.Location.Longitude; lon != 0 { + longitude = fmt.Sprintf("%.6f", lon) + } + } else { + log.Printf("City lookup error: %v", err) + } + } else { + log.Printf("Error opening City database: %v", err) + } + + // Lookup ASN Information + if asnDb, err := geoip2.Open(asnDbPath); err == nil { + defer asnDb.Close() + + if asnRecord, err := asnDb.ASN(ip); err == nil { + if asnNum := asnRecord.AutonomousSystemNumber; asnNum != 0 { + asn = fmt.Sprintf("%d", asnNum) + } + if org := asnRecord.AutonomousSystemOrganization; org != "" { + isp = org + } + } else { + log.Printf("ASN lookup error: %v", err) + } + } else { + log.Printf("Error opening ASN database: %v", err) + } + + response := fmt.Sprintf( + "IP: %s | Location: %s, %s, %s | Coordinates: %s, %s | ASN: %s | ISP: %s", + ip.String(), city, subdivision, country, latitude, longitude, asn, isp, + ) + + irccon.Privmsg(e.Arguments[0], response) +}