diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5b6c096 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +config.toml diff --git a/README.md b/README.md index e69de29..b0fc2e1 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,29 @@ +# Discord Regex Monitor + +### Example Config +```toml +[DISCORD] + BOT_TOKEN = "" + +[[SERVERS]] + SERVER_ID = "1050204101826334999" + OUTPUT_CHANNEL_ID = "1250585834726621999" + PREFIX_ENABLED = true + ALLOW_DUPLICATES = false + [SERVERS.COIN_REGEXES] + Bitcoin = "[13][a-km-zA-HJ-NP-Z1-9]{25,34}" + Ethereum = "0x[a-fA-F0-9]{40}" + [SERVERS.CHANNEL_BLACKLIST] + CHANNELS = ["1250537334550827078"] + +[[SERVERS]] + SERVER_ID = "1250885747062345999" + OUTPUT_CHANNEL_ID = "1250585834726621999" + PREFIX_ENABLED = true + ALLOW_DUPLICATES = false + [SERVERS.COIN_REGEXES] + Ethereum = "0x[a-fA-F0-9]{40}" + Solana = "[1-9A-HJ-NP-Za-km-z]{32,44}" + [SERVERS.CHANNEL_BLACKLIST] + CHANNELS = [] +``` diff --git a/config.toml.example b/config.toml.example new file mode 100644 index 0000000..abbe7c2 --- /dev/null +++ b/config.toml.example @@ -0,0 +1,24 @@ +[DISCORD] + BOT_TOKEN = "" + +[[SERVERS]] + SERVER_ID = "" + OUTPUT_CHANNEL_ID = "" + PREFIX_ENABLED = true + [SERVERS.COIN_REGEXES] + Bitcoin = "[13][a-km-zA-HJ-NP-Z1-9]{25,34}" + Ethereum = "0x[a-fA-F0-9]{40}" + [SERVERS.CHANNEL_BLACKLIST] + CHANNELS = [""] + +[[SERVERS]] + SERVER_ID = "" + OUTPUT_CHANNEL_ID = "" + PREFIX_ENABLED = false + [SERVERS.COIN_REGEXES] + Ethereum = "0x[a-fA-F0-9]{40}" + Solana = "[1-9A-HJ-NP-Za-km-z]{32,44}" + [SERVERS.CHANNEL_BLACKLIST] + CHANNELS = [] + + diff --git a/go.mod b/go.mod index e12cebf..9d5647b 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,11 @@ module git.directme.in/Joren/SolMonitor go 1.22.4 -require github.com/bwmarrin/discordgo v0.28.1 +require ( + git.directme.in/Joren/SmsHook v1.0.2 + github.com/BurntSushi/toml v1.4.0 + github.com/bwmarrin/discordgo v0.28.1 +) require ( github.com/gorilla/websocket v1.4.2 // indirect diff --git a/main.go b/main.go index 49e4195..b96be03 100644 --- a/main.go +++ b/main.go @@ -3,19 +3,66 @@ package main import ( "fmt" "os" + "regexp" + "github.com/BurntSushi/toml" "github.com/bwmarrin/discordgo" + "git.directme.in/Joren/SmsHook/ringbuffer" ) + var ( - Token string + Token string + Servers []ServerConfig + CoinRegexes map[string]*regexp.Regexp + ChannelBlacklist map[string][]string + MessageHistory map[string]*ringbuffer.RingBuffer ) +type ServerConfig struct { + ServerID string `toml:"SERVER_ID"` + OutputChannelID string `toml:"OUTPUT_CHANNEL_ID"` + CoinRegexes map[string]string `toml:"COIN_REGEXES"` + ChannelBlacklist struct { + Channels []string `toml:"CHANNELS"` + } `toml:"CHANNEL_BLACKLIST"` + PrefixEnabled bool `toml:"PREFIX_ENABLED"` + AllowDuplicates bool `toml:"ALLOW_DUPLICATES"` +} + func init() { - Token = os.Getenv("DISCORD_BOT_TOKEN") + var config struct { + Discord struct { + BotToken string `toml:"BOT_TOKEN"` + } `toml:"DISCORD"` + Servers []ServerConfig `toml:"SERVERS"` + } + + if _, err := toml.DecodeFile("config.toml", &config); err != nil { + fmt.Println("Error loading config:", err) + os.Exit(1) + } + + Token = config.Discord.BotToken + Servers = config.Servers + + CoinRegexes = make(map[string]*regexp.Regexp) + ChannelBlacklist = make(map[string][]string) + MessageHistory = make(map[string]*ringbuffer.RingBuffer) + + for _, server := range Servers { + for coin, regex := range server.CoinRegexes { + fullRegex := fmt.Sprintf(`\b%s\b`, regex) + CoinRegexes[coin] = regexp.MustCompile(fullRegex) + } + ChannelBlacklist[server.ServerID] = server.ChannelBlacklist.Channels + + MessageHistory[server.ServerID] = ringbuffer.NewRingBuffer(4096) + } + if Token == "" { - fmt.Println("No token provided. Please set DISCORD_BOT_TOKEN environment variable.") + fmt.Println("No token provided in config.toml.") os.Exit(1) } } @@ -28,8 +75,9 @@ func main() { } dg.AddHandler(ready) + dg.AddHandler(messageCreate) - dg.Identify.Intents = discordgo.IntentsGuilds | discordgo.IntentsGuildMessages + dg.Identify.Intents = discordgo.IntentsGuilds | discordgo.IntentsGuildMessages | discordgo.IntentMessageContent err = dg.Open() if err != nil { @@ -42,6 +90,89 @@ func main() { } func ready(s *discordgo.Session, event *discordgo.Ready) { - s.UpdateGameStatus(0, "Monitoring addresses") + s.UpdateGameStatus(0, "Monitoring messages") +} + +func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) { + if m.Author.ID == s.State.User.ID { + return + } + + for _, server := range Servers { + if m.GuildID == server.ServerID { + channel, err := s.State.Channel(m.ChannelID) + if err != nil { + fmt.Println("Error getting channel:", err) + return + } + + if isChannelBlacklisted(server.ServerID, channel.ID) { + return + } + + checkMessageContent(s, server, m) + checkEmbeds(s, server, m) + return + } + } +} + +func checkMessageContent(s *discordgo.Session, server ServerConfig, m *discordgo.MessageCreate) { + for coin, regex := range CoinRegexes { + match := regex.FindStringSubmatch(m.Content) + if len(match) > 0 { + matchedAddress := match[0] + message := formatMessage(server, coin, m.Author.Username, matchedAddress) + + if !server.AllowDuplicates && MessageHistory[server.ServerID].ContainsItem(message) { + return + } + + s.ChannelMessageSend(server.OutputChannelID, message) + MessageHistory[server.ServerID].Add(message) + } + } +} + +func checkEmbeds(s *discordgo.Session, server ServerConfig, m *discordgo.MessageCreate) { + for _, embed := range m.Message.Embeds { + if embed.Type == "rich" && embed.Description != "" { + for coin, regex := range CoinRegexes { + match := regex.FindStringSubmatch(embed.Description) + if len(match) > 0 { + matchedAddress := match[0] + message := formatMessage(server, coin, m.Author.Username, matchedAddress) + + if !server.AllowDuplicates && MessageHistory[server.ServerID].ContainsItem(message) { + return + } + + s.ChannelMessageSend(server.OutputChannelID, message) + MessageHistory[server.ServerID].Add(message) + } + } + } + } +} + +func formatMessage(server ServerConfig, coin, username, address string) string { + if server.PrefixEnabled { + return fmt.Sprintf("%s: %s", coin, address) + } + return address +} + +func isChannelBlacklisted(serverID, channelID string) bool { + blacklistedChannels, ok := ChannelBlacklist[serverID] + if !ok { + return false + } + + for _, id := range blacklistedChannels { + if id == channelID { + return true + } + } + return false }