Files
linkbomber/main.py
2025-07-15 22:40:51 -04:00

129 lines
3.9 KiB
Python

import discord
import json
import re
import os
from urllib.parse import urlparse, parse_qs, urlencode, urlunparse
from discord import app_commands
from dotenv import load_dotenv
load_dotenv()
def load_json(filename):
try:
with open(filename, "r", encoding="utf-8") as f:
return json.load(f)
except FileNotFoundError:
return {}
def save_json(data, filename):
with open(filename, "w", encoding="utf-8") as f:
json.dump(data, f, indent=4)
rules = load_json("url_rules.json")
server_settings = load_json("server_settings.json")
intents = discord.Intents.default()
intents.message_content = True
client = discord.Client(intents=intents)
tree = app_commands.CommandTree(client)
def clean_url(url):
parsed_url = urlparse(url)
providers = [p for p in rules["providers"].items() if p[0] != "globalRules"] + [
("globalRules", rules["providers"].get("globalRules", {}))
]
for _, data in providers:
if re.match(data.get("urlPattern", ""), url):
query_params = parse_qs(parsed_url.query, keep_blank_values=True)
filtered_params = {
k: v
for k, v in query_params.items()
if not any(re.fullmatch(rule, k) for rule in data.get("rules", []))
}
cleaned_query = urlencode(filtered_params, doseq=True)
return (
urlunparse((
parsed_url.scheme,
parsed_url.netloc,
parsed_url.path,
parsed_url.params,
cleaned_query,
parsed_url.fragment,
))
if cleaned_query != parsed_url.query
else None
)
return None
@client.event
async def on_ready():
await tree.sync()
@client.event
async def on_message(message):
if message.author == client.user or server_settings.get(
str(message.guild.id), {}
).get("disabled", False):
return
for word in message.content.split():
if word.startswith(("http://", "https://")) and (cleaned := clean_url(word)):
await message.channel.send(f"Cleaned link: {cleaned}")
break
@tree.command(
name="toggle",
description="Toggle automatic link cleaning for this server (requires Manage Messages permission).",
)
async def toggle(interaction: discord.Interaction):
if not interaction.user.guild_permissions.manage_messages:
await interaction.response.send_message(
"You need 'Manage Messages' permission to use this command.", ephemeral=True
)
return
guild_id = str(interaction.guild.id)
server_settings[guild_id] = {
"disabled": not server_settings.get(guild_id, {}).get("disabled", False)
}
save_json(server_settings, "server_settings.json")
await interaction.response.send_message(
f"Link cleaning {'disabled' if server_settings[guild_id]['disabled'] else 'enabled'}."
)
@tree.command(
name="bomb", description="Cleans the first link found in the last 20 messages."
)
async def bomb(interaction: discord.Interaction):
async for msg in interaction.channel.history(limit=20):
for word in msg.content.split():
if word.startswith(("http://", "https://")) and (
cleaned := clean_url(word)
):
await interaction.response.send_message(f"Cleaned link: {cleaned}")
return
await interaction.response.send_message("No links found.")
@tree.command(
name="about",
description="Explains the purpose of the bot and its privacy features.",
)
async def about(interaction: discord.Interaction):
await interaction.response.send_message(
"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."
)
TOKEN = os.getenv("DISCORD_BOT_TOKEN")
client.run(TOKEN)