129 lines
3.9 KiB
Python
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)
|