rewrite
This commit is contained in:
238
main.go
Normal file
238
main.go
Normal file
@@ -0,0 +1,238 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/bwmarrin/discordgo"
|
||||
)
|
||||
|
||||
var (
|
||||
rules map[string]any
|
||||
serverSettings map[string]any
|
||||
settingsMutex sync.RWMutex
|
||||
)
|
||||
|
||||
func loadJSON(filename string) map[string]any {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return map[string]any{}
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
data, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
return map[string]any{}
|
||||
}
|
||||
|
||||
var result map[string]any
|
||||
err = json.Unmarshal(data, &result)
|
||||
if err != nil {
|
||||
return map[string]any{}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func saveJSON(data map[string]any, filename string) {
|
||||
jsonData, _ := json.MarshalIndent(data, "", " ")
|
||||
os.WriteFile(filename, jsonData, 0644)
|
||||
}
|
||||
|
||||
func cleanURL(rawURL string) string {
|
||||
parsedURL, err := url.Parse(rawURL)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
rulesMap := rules["providers"].(map[string]any)
|
||||
providers := make([]map[string]any, 0, len(rulesMap))
|
||||
for key, value := range rulesMap {
|
||||
if key != "globalRules" {
|
||||
providers = append(providers, map[string]any{key: value})
|
||||
}
|
||||
}
|
||||
if globalRules, ok := rulesMap["globalRules"].(map[string]any); ok {
|
||||
providers = append(providers, map[string]any{"globalRules": globalRules})
|
||||
}
|
||||
|
||||
for _, provider := range providers {
|
||||
for _, data := range provider {
|
||||
dataMap := data.(map[string]any)
|
||||
pattern := dataMap["urlPattern"].(string)
|
||||
if matched, _ := regexp.MatchString(pattern, rawURL); matched {
|
||||
// Check exceptions
|
||||
if exceptions, ok := dataMap["exceptions"].([]any); ok {
|
||||
for _, exc := range exceptions {
|
||||
if excMatched, _ := regexp.MatchString(exc.(string), rawURL); excMatched {
|
||||
return "" // Skip cleaning for exceptions
|
||||
}
|
||||
}
|
||||
}
|
||||
// Proceed to filter params
|
||||
queryParams, _ := url.ParseQuery(parsedURL.RawQuery)
|
||||
rulesList := dataMap["rules"].([]any)
|
||||
// Include referralMarketing if present
|
||||
if referralMarketing, ok := dataMap["referralMarketing"].([]any); ok {
|
||||
rulesList = append(rulesList, referralMarketing...)
|
||||
}
|
||||
filtered := make(url.Values)
|
||||
for k, v := range queryParams {
|
||||
shouldFilter := false
|
||||
for _, rule := range rulesList {
|
||||
if matched, _ := regexp.MatchString(rule.(string), k); matched {
|
||||
shouldFilter = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !shouldFilter {
|
||||
filtered[k] = v
|
||||
}
|
||||
}
|
||||
parsedURL.RawQuery = filtered.Encode()
|
||||
return parsedURL.String() // Always return the cleaned URL if pattern matches
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func getServerDisabled(guildID string) bool {
|
||||
settingsMutex.RLock()
|
||||
defer settingsMutex.RUnlock()
|
||||
if guildSettings, ok := serverSettings[guildID].(map[string]any); ok {
|
||||
if d, ok := guildSettings["disabled"].(bool); ok {
|
||||
return d
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func setServerDisabled(guildID string, disabled bool, filename string) {
|
||||
settingsMutex.Lock()
|
||||
defer settingsMutex.Unlock()
|
||||
if serverSettings[guildID] == nil {
|
||||
serverSettings[guildID] = map[string]any{}
|
||||
}
|
||||
serverSettings[guildID].(map[string]any)["disabled"] = disabled
|
||||
saveJSON(serverSettings, filename)
|
||||
}
|
||||
|
||||
func findFirstCleanLink(words []string) string {
|
||||
for _, word := range words {
|
||||
if strings.HasPrefix(word, "http://") || strings.HasPrefix(word, "https://") {
|
||||
if cleaned := cleanURL(word); cleaned != "" {
|
||||
return fmt.Sprintf("Cleaned link: <%s>", cleaned)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func onReady(s *discordgo.Session, event *discordgo.Ready) {
|
||||
log.Println("Bot is ready")
|
||||
commands := []*discordgo.ApplicationCommand{
|
||||
{Name: "toggle", Description: "Toggle automatic link cleaning for this server (requires Manage Messages permission)."},
|
||||
{Name: "bomb", Description: "Cleans the first link found in the last 20 messages."},
|
||||
{Name: "about", Description: "Explains the purpose of the bot and its privacy features."},
|
||||
}
|
||||
for _, cmd := range commands {
|
||||
_, err := s.ApplicationCommandCreate(s.State.User.ID, "", cmd)
|
||||
if err != nil {
|
||||
log.Printf("Cannot create '%s' command: %v", cmd.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func onMessage(s *discordgo.Session, m *discordgo.MessageCreate) {
|
||||
if m.Author.ID == s.State.User.ID || getServerDisabled(m.GuildID) {
|
||||
return
|
||||
}
|
||||
if msg := findFirstCleanLink(strings.Fields(m.Content)); msg != "" {
|
||||
s.ChannelMessageSend(m.ChannelID, msg)
|
||||
}
|
||||
}
|
||||
|
||||
func onInteraction(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||
if i.Type != discordgo.InteractionApplicationCommand {
|
||||
return
|
||||
}
|
||||
data := i.ApplicationCommandData()
|
||||
switch data.Name {
|
||||
case "toggle":
|
||||
if i.Member == nil || (i.Member.Permissions&discordgo.PermissionManageMessages) == 0 {
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{Content: "You need 'Manage Messages' permission to use this command.", Flags: discordgo.MessageFlagsEphemeral}})
|
||||
return
|
||||
}
|
||||
guildID := i.GuildID
|
||||
newDisabled := !getServerDisabled(guildID)
|
||||
setServerDisabled(guildID, newDisabled, settingsPath)
|
||||
status := "enabled"
|
||||
if newDisabled {
|
||||
status = "disabled"
|
||||
}
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{Content: fmt.Sprintf("Link cleaning %s.", status)}})
|
||||
|
||||
case "bomb":
|
||||
messages, err := s.ChannelMessages(i.ChannelID, 20, "", "", "")
|
||||
msg := "No links found."
|
||||
if err == nil {
|
||||
for _, m := range messages {
|
||||
if link := findFirstCleanLink(strings.Fields(m.Content)); link != "" {
|
||||
msg = link
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
msg = "Error retrieving messages."
|
||||
}
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{Content: msg}})
|
||||
|
||||
case "about":
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{Content: "This bot removes tracking parameters from links to improve privacy. Use `/toggle` to enable/disable auto-cleaning, `/bomb` to clean a recent link manually, and `/about` for info."}})
|
||||
}
|
||||
}
|
||||
|
||||
var settingsPath string
|
||||
|
||||
func main() {
|
||||
rulesPath := flag.String("rules", "url_rules.json", "path to url_rules.json file")
|
||||
settingsPathFlag := flag.String("settings", "server_settings.json", "path to server_settings.json file")
|
||||
token := flag.String("token", "", "Discord bot token")
|
||||
flag.Parse()
|
||||
|
||||
settingsPath = *settingsPathFlag
|
||||
|
||||
if *token == "" {
|
||||
log.Fatal("Bot token not provided. Use -token flag or set LINKBOMBER_BOT_TOKEN environment variable")
|
||||
}
|
||||
|
||||
rules = loadJSON(*rulesPath)
|
||||
serverSettings = loadJSON(settingsPath)
|
||||
|
||||
session, err := discordgo.New("Bot " + *token)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
session.Identify.Intents = discordgo.IntentsGuilds | discordgo.IntentsGuildMessages | discordgo.IntentsMessageContent
|
||||
|
||||
session.AddHandler(onReady)
|
||||
session.AddHandler(onMessage)
|
||||
session.AddHandler(onInteraction)
|
||||
|
||||
if err = session.Open(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
log.Println("Linkbomber is running. Press Ctrl+C to exit.")
|
||||
<-make(chan struct{})
|
||||
}
|
||||
Reference in New Issue
Block a user