Compare commits
10 Commits
2d6d9c3959
...
c77c1c049d
Author | SHA1 | Date |
---|---|---|
|
c77c1c049d | |
|
fa8a3e3e95 | |
|
569561a2a7 | |
|
9d2cc2674d | |
|
fcbee25fa0 | |
|
c34943807f | |
|
2ee9a09aa2 | |
|
21975e9e47 | |
|
a0cc3c3e0a | |
|
5888a7821b |
|
@ -1 +1,4 @@
|
|||
*
|
||||
*.mmdb
|
||||
config.*
|
||||
.git*
|
||||
irc_bot
|
||||
|
|
33
config.go
33
config.go
|
@ -1,33 +0,0 @@
|
|||
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
|
||||
}
|
|
@ -4,6 +4,8 @@
|
|||
"ssl": true,
|
||||
"nickname": "Some_nick",
|
||||
"channels": ["#channel", "#channel2"],
|
||||
"geoip_database": "/app/geolite2-city.mmdb",
|
||||
"geoip_asn": "/app/geolite2-asn.mmdb"
|
||||
"geoip_city": "/app/geolite2-city.mmdb",
|
||||
"geoip_asn": "/app/geolite2-asn.mmdb",
|
||||
"callback": false,
|
||||
"debug": false
|
||||
}
|
||||
|
|
17
dockerfile
17
dockerfile
|
@ -1,20 +1,21 @@
|
|||
FROM synt/musl-cross-make AS builder
|
||||
FROM null31/musl-cross-make:x86_64 AS builder
|
||||
|
||||
ARG GO_VERSION=1.23.0
|
||||
|
||||
ADD https://fg.q0s.de/null31/irc_bot.git /app
|
||||
ENV CGO_ENABLED=1 CC=x86_64-linux-musl-gcc GOOS=linux GOARCH=amd64 PATH=$PATH:/usr/local/go/bin
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ENV CGO_ENABLED=1 CC=x86_64-linux-musl-gcc GOOS=linux GOARCH=amd64 PATH=$PATH:/musl-cross-make/output/bin:/usr/local/go/bin
|
||||
RUN apt update && apt install -y curl && \
|
||||
curl -sSOL https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz && \
|
||||
tar -zxf go${GO_VERSION}.linux-amd64.tar.gz -C /usr/local
|
||||
|
||||
RUN wget https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz && tar -zxf go${GO_VERSION}.linux-amd64.tar.gz -C /usr/local && \
|
||||
go build -a -ldflags '-extldflags "-static"' -o irc_bot
|
||||
RUN --mount=type=bind,target=. go build -a -ldflags '-extldflags "-static"' -o /tmp/irc_bot
|
||||
|
||||
# build the final image
|
||||
FROM alpine:3.20
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=builder /app/irc_bot /app/
|
||||
COPY --from=builder /tmp/irc_bot /app/
|
||||
|
||||
CMD ["./irc_bot"]
|
||||
CMD ["./irc_bot"]
|
||||
|
|
7
go.mod
7
go.mod
|
@ -3,9 +3,12 @@ module irc_bot
|
|||
go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/oschwald/geoip2-golang v1.11.0 // indirect
|
||||
github.com/oschwald/geoip2-golang v1.11.0
|
||||
github.com/thoj/go-ircevent v0.0.0-20210723090443-73e444401d64
|
||||
)
|
||||
|
||||
require (
|
||||
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
|
||||
|
|
8
go.sum
8
go.sum
|
@ -1,7 +1,13 @@
|
|||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
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=
|
||||
|
@ -14,3 +20,5 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn
|
|||
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=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
129
main.go
129
main.go
|
@ -3,23 +3,24 @@ package main
|
|||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
"crypto/tls"
|
||||
|
||||
"github.com/oschwald/geoip2-golang"
|
||||
"github.com/thoj/go-ircevent"
|
||||
"irc_bot/utils"
|
||||
)
|
||||
|
||||
func main() {
|
||||
config, err := LoadConfig("config.json")
|
||||
// Load configuration
|
||||
config, err := utils.LoadConfig("config.json")
|
||||
if err != nil {
|
||||
log.Fatalf("Error loading config: %v", err)
|
||||
}
|
||||
|
||||
// Initialize the IRC bot
|
||||
irccon := irc.IRC(config.Nickname, config.Nickname)
|
||||
irccon.VerboseCallbackHandler = true
|
||||
irccon.Debug = true
|
||||
irccon.VerboseCallbackHandler = config.Debug
|
||||
irccon.Debug = config.Callback
|
||||
irccon.UseTLS = config.SSL
|
||||
irccon.TLSConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
|
||||
|
@ -34,6 +35,7 @@ func main() {
|
|||
}
|
||||
})
|
||||
|
||||
// Read the messages to get the content and call the apropriate command if has
|
||||
irccon.AddCallback("PRIVMSG", func(e *irc.Event) {
|
||||
if len(e.Arguments) < 2 {
|
||||
return
|
||||
|
@ -46,108 +48,25 @@ func main() {
|
|||
}
|
||||
|
||||
cmd := strings.TrimPrefix(parts[0], "!")
|
||||
if commandFunc, ok := commands[cmd]; ok {
|
||||
commandFunc(irccon, e, parts)
|
||||
switch {
|
||||
// For each new command, add a new case
|
||||
case cmd == "geoip":
|
||||
// Call GeoIP command that return the query data
|
||||
response, err := utils.HandleGeoIPCommand(parts[1], config.GeoIP_City, config.GeoIP_ASN)
|
||||
if err != nil {
|
||||
irccon.Privmsg(e.Arguments[0], fmt.Sprintf("%v", err))
|
||||
} else {
|
||||
irccon.Privmsg(e.Arguments[0], response)
|
||||
}
|
||||
|
||||
case cmd == "commands":
|
||||
// List which are the available commands to use
|
||||
irccon.Privmsg(e.Arguments[0], "Current available commands: !geoip")
|
||||
|
||||
default:
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
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 <IP or domain>")
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
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"`
|
||||
GeoIP_City string `json:"geoip_city"`
|
||||
GeoIP_ASN string `json:"geoip_asn"`
|
||||
Callback bool `json:"callback"`
|
||||
Debug bool `json:"debug"`
|
||||
}
|
||||
|
||||
func LoadConfig(file string) (Config, error) {
|
||||
var config Config
|
||||
|
||||
configFile, err := os.Open(file)
|
||||
if err != nil {
|
||||
return config, fmt.Errorf("error opening config file: %v", err)
|
||||
}
|
||||
defer configFile.Close()
|
||||
|
||||
err = json.NewDecoder(configFile).Decode(&config)
|
||||
if err != nil {
|
||||
return config, fmt.Errorf("error decoding config file: %v", err)
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"net"
|
||||
"fmt"
|
||||
|
||||
"github.com/oschwald/geoip2-golang"
|
||||
)
|
||||
|
||||
func HandleGeoIPCommand(ipAddress string, dbCity string, dbASN string) (string, error) {
|
||||
ip := net.ParseIP(ipAddress)
|
||||
|
||||
// Check if IP/Hostname is valid
|
||||
if ip == nil {
|
||||
resolvedIP, err := net.LookupIP(ipAddress)
|
||||
if err != nil || len(resolvedIP) == 0 {
|
||||
return "", fmt.Errorf("Invalid IP or hostname: %s", ipAddress)
|
||||
}
|
||||
ip = resolvedIP[0]
|
||||
}
|
||||
|
||||
var (
|
||||
city = "N/A"
|
||||
state = "N/A"
|
||||
country = "N/A"
|
||||
asn = "N/A"
|
||||
isp = "N/A"
|
||||
)
|
||||
|
||||
// Lookup City, State and Country info
|
||||
if cityDb, err := geoip2.Open(dbCity); 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 != "" {
|
||||
state = name
|
||||
}
|
||||
}
|
||||
|
||||
if name := cityRecord.Country.Names["en"]; name != "" {
|
||||
country = name
|
||||
}
|
||||
} else {
|
||||
return "", fmt.Errorf("City Lookup error: %v", err)
|
||||
}
|
||||
} else {
|
||||
return "", fmt.Errorf("Error opening City database: %v", err)
|
||||
}
|
||||
|
||||
// Lookup ASN info
|
||||
if asnDb, err := geoip2.Open(dbASN); 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 {
|
||||
return "", fmt.Errorf("ASN lookup error: %v", err)
|
||||
}
|
||||
} else {
|
||||
return "", fmt.Errorf("Error opening ASN database: %v", err)
|
||||
}
|
||||
|
||||
// Format the response
|
||||
response := fmt.Sprintf(
|
||||
"IP: %s | Location: %s, %s, %s | ASN: %s | ISP: %s",
|
||||
ip.String(), city, state, country, asn, isp,
|
||||
)
|
||||
|
||||
return response, nil
|
||||
}
|
Loading…
Reference in New Issue