This commit is contained in:
pancakes-proxy 2025-05-10 03:30:40 +09:00
commit 121e0d9955
10 changed files with 443 additions and 5 deletions

10
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,10 @@
# The Docker image that will be used to build your app
image: node:lts
create-pages:
pages:
# The folder that contains the files to be exposed at the Page URL
publish: website
rules:
# This ensures that only pushes to the default branch will trigger
# a pages deploy
- if: $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH

View File

@ -49,7 +49,7 @@ WDiscordBot Rules and Security Policies
3. **Attribution Requirements:**
- Any derivative work or modification of the bots code must include a clear credit line to the original developers:
```
Original Code Developed by Zacarias Posey, Izzy Perez, Milly, and JW.
Original Code Developed by Zacarias Posey, Izzy Perez, Milly, Lillian, and JW.
```
- Failure to include attribution may result in removal requests, legal action, or a copyright takedown notice.

View File

@ -455,8 +455,9 @@ Server Rules:
{SERVER_RULES}
---
Channel Context:
You will be provided with the following information about the channel where the message was sent:
User and Channel Context:
You will be provided with the following information:
- User's Server Role: The role of the user in the server (e.g., "Server Owner", "Admin", "Moderator", "Member").
- Channel Category: The name of the category the channel belongs to (e.g., "General", "NSFW Zone", "Gaming").
- Channel Age-Restricted (Discord Setting): A boolean (true/false) indicating if the channel is marked as age-restricted (NSFW) in Discord's settings.
@ -521,6 +522,20 @@ Example Response (Suicidal Content):
}}
"""
member = message.author # This is a discord.Member object
server_role_str = "Member" # Default
if member == message.guild.owner:
server_role_str = "Server Owner"
elif member.guild_permissions.administrator:
server_role_str = "Admin"
else:
# Check for generic moderator permissions if not owner or admin
perms = member.guild_permissions
if perms.manage_messages or perms.kick_members or perms.ban_members or perms.moderate_members:
server_role_str = "Moderator"
# else, it remains "Member"
user_prompt_content_list = [
{
"type": "text",
@ -531,6 +546,7 @@ Example Response (Suicidal Content):
Message Details:
- Author: {message.author.name} (ID: {message.author.id})
- Server Role: {server_role_str}
- Channel: #{message.channel.name} (ID: {message.channel.id})
- Channel Category: {message.channel.category.name if message.channel.category else "No Category"}
- Channel Age-Restricted (Discord Setting): {message.channel.is_nsfw()}
@ -654,7 +670,7 @@ Now, analyze the provided message content based on the rules and instructions gi
try:
mod_log_api_secret = os.getenv("MOD_LOG_API_SECRET")
if mod_log_api_secret:
post_url = f"https://slipstreamm.dev/dashboard/api/guilds/{guild_id}/ai-moderation-action"
post_url = f"https://slipstreamm.dev/dashboard/api/guilds/{guild_id}/ai-moderation-action" #will be replaceing later with the Learnhelp API
payload = {
"timestamp": current_timestamp_iso,
"guild_id": guild_id,
@ -705,7 +721,7 @@ Now, analyze the provided message content based on the rules and instructions gi
msg_content = message.content if message.content else "*No text content*"
notification_embed.add_field(name="Message Content", value=msg_content[:1024], inline=False)
# Use the model_used variable that was defined earlier
notification_embed.set_footer(text=f"AI Model: {model_used}")
notification_embed.set_footer(text=f"AI Model: {model_used}. Learnhelp AI Moderation.")
notification_embed.timestamp = discord.utils.utcnow() # Using discord.utils.utcnow() which is still supported
action_taken_message = "" # To append to the notification

93
cogs/commits.py Normal file
View File

@ -0,0 +1,93 @@
import discord
from discord import app_commands
from discord.ext import commands
import aiohttp
import urllib.parse
# A view with a button to display commit diffs.
class DiffView(discord.ui.View):
def __init__(self, diffs):
super().__init__(timeout=60) # Button active for 60 seconds.
self.diffs = diffs
@discord.ui.button(label="Show Diffs", style=discord.ButtonStyle.primary)
async def show_diffs(self, button: discord.ui.Button, interaction: discord.Interaction):
diff_message = ""
# Format each file's diff info.
for diff in self.diffs:
file_path = diff.get("new_path", "Unknown file")
diff_text = diff.get("diff", "No diff available")
diff_message += f"**File:** {file_path}\n```diff\n{diff_text}\n```\n"
# Truncate if too long.
if len(diff_message) > 1900:
diff_message = diff_message[:1900] + "\n...[truncated]"
await interaction.response.send_message(diff_message, ephemeral=True)
# The cog defining the /changes command.
class GitlabChanges(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
self.repo = "pancakes1234/wdiscordbotserver"
# URL-encode the repository identifier for the GitLab API.
self.project_id = urllib.parse.quote_plus(self.repo)
@app_commands.command(
name="changes",
description="Display details of the latest commit for the bot"
)
async def changes(self, interaction: discord.Interaction):
# Defer the response to allow for asynchronous API calls.
await interaction.response.defer()
# Fetch the list of commits via GitLab's API.
async with aiohttp.ClientSession() as session:
commits_url = f"https://gitlab.com/api/v4/projects/{self.project_id}/repository/commits"
async with session.get(commits_url) as resp:
if resp.status != 200:
await interaction.followup.send("Failed to fetch commits from GitLab.")
return
commits = await resp.json()
if not commits:
await interaction.followup.send("No commits found.")
return
# Retrieve the most recent commit.
latest_commit = commits[0]
commit_hash = latest_commit.get("id", "N/A")
commit_message = latest_commit.get("message", "No commit message provided.")
author_name = latest_commit.get("author_name", "Unknown author")
# Construct a clickable link for the commit.
commit_url = f"https://gitlab.com/{self.repo}/-/commit/{commit_hash}"
# Fetch diff details for the commit.
async with aiohttp.ClientSession() as session:
diff_url = f"https://gitlab.com/api/v4/projects/{self.project_id}/repository/commits/{commit_hash}/diff"
async with session.get(diff_url) as diff_resp:
if diff_resp.status != 200:
diffs = []
files_field = "Could not fetch diff details."
else:
diffs = await diff_resp.json()
file_list = [diff.get("new_path", "Unknown file") for diff in diffs]
files_field = "\n".join(file_list) if file_list else "No files changed."
# Build the embed with commit details.
embed = discord.Embed(
title="Latest Commit",
description=f"Latest commit from **{self.repo}**",
color=discord.Color.blurple()
)
embed.add_field(name="Commit Hash", value=commit_hash, inline=False)
embed.add_field(name="Author", value=author_name, inline=False)
embed.add_field(name="Description", value=commit_message, inline=False)
embed.add_field(name="Files Edited", value=files_field, inline=False)
embed.add_field(name="Commit Link", value=f"[View Commit]({commit_url})", inline=False)
# Attach the diff button view so the user can check diffs on demand.
view = DiffView(diffs)
await interaction.followup.send(embed=embed, view=view)
# Standard setup function for loading the cog.
async def setup(bot: commands.Bot):
await bot.add_cog(GitlabChanges(bot))

158
cogs/letsgogambling.py Normal file
View File

@ -0,0 +1,158 @@
import discord
from discord import app_commands
from discord.ext import commands
import random
from typing import Optional
# A sample shop inventory. Here the keys are item names (in lowercase)
# and the values are the cost. When selling, we assume you get about half the price.
SHOP_ITEMS = {
"pen": 20,
"notebook": 50,
"backpack": 100,
"headphones": 200,
"Temu Iphone 69326": 75,
"Temu Macbook water": 100,
"phone": 150,
"Dell Optiplex 3020 M": 200,
"Asus Vivobook 14": 175,
"Kasane Teto plushie": 50,
"miku plushie": 50,
"RARE Golden Teto Plusie": 20000,
}
class Fun(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
# A simple in-memory economy store where each user starts with $100 and an empty inventory.
self.economy = {} # {user_id: {"balance": int, "inventory": list}}
def get_account(self, user_id: int) -> dict:
"""Creates an account for the user if one does not exist and returns it."""
if user_id not in self.economy:
self.economy[user_id] = {"balance": 100, "inventory": []}
return self.economy[user_id]
# Create a slash command group called "fun"
fun_group = app_commands.Group(name="fun", description="Fun economy commands")
@fun_group.command(name="work", description="Work a job and earn money!")
async def work(self, interaction: discord.Interaction):
account = self.get_account(interaction.user.id)
earned = random.randint(50, 150)
job = random.choice(["barista", "cashier", "developer", "bartender", "freelancer"])
account["balance"] += earned
await interaction.response.send_message(
f"You worked as a {job} and earned ${earned}.\nYour new balance is ${account['balance']}."
)
@fun_group.command(name="sell", description="Sell an item from your inventory.")
@app_commands.describe(item="The item you wish to sell.")
async def sell(self, interaction: discord.Interaction, item: str):
account = self.get_account(interaction.user.id)
# Find the item in your inventory (ignoring case)
item_in_inventory = None
for inv_item in account["inventory"]:
if inv_item.lower() == item.lower():
item_in_inventory = inv_item
break
if not item_in_inventory:
await interaction.response.send_message("You don't have that item in your inventory!")
return
# Remove the item and determine its selling price.
account["inventory"].remove(item_in_inventory)
# Use the shop price if known, otherwise default to $20; you get roughly half when selling.
sold_price = SHOP_ITEMS.get(item_in_inventory.lower(), 20) // 2
account["balance"] += sold_price
await interaction.response.send_message(
f"You sold your {item_in_inventory} for ${sold_price}.\nYour new balance is ${account['balance']}."
)
@fun_group.command(name="steal", description="Attempt to steal money from another user!")
@app_commands.describe(target="The member you want to steal from.")
async def steal(self, interaction: discord.Interaction, target: discord.Member):
# Prevent stealing from oneself
if target.id == interaction.user.id:
await interaction.response.send_message("You can't steal from yourself!")
return
thief = self.get_account(interaction.user.id)
victim = self.get_account(target.id)
if victim["balance"] < 50:
await interaction.response.send_message(f"{target.display_name} doesn't have enough money to steal!")
return
# Simulate theft with a 30% chance of success.
if random.random() < 0.3:
# On success: steal a random amount between $10 and up to one-third of the victim's balance (max $100).
stolen = random.randint(10, min(100, victim["balance"] // 3))
victim["balance"] -= stolen
thief["balance"] += stolen
await interaction.response.send_message(
f"You successfully stole ${stolen} from {target.display_name}!\nYour new balance is ${thief['balance']}."
)
else:
# On failure, the thief is fined a small amount.
fine = random.randint(5, 20)
thief["balance"] = max(0, thief["balance"] - fine)
await interaction.response.send_message(
f"You got caught trying to steal from {target.display_name}!\nYou were fined ${fine}.\nYour new balance is ${thief['balance']}."
)
@fun_group.command(name="shop", description="View shop items or buy an item.")
@app_commands.describe(item="The item you wish to buy (optional). Leave empty to view available items.")
async def shop(self, interaction: discord.Interaction, item: Optional[str] = None):
account = self.get_account(interaction.user.id)
if item is None:
# List all available shop items.
items_list = "\n".join([f"{name.title()} - ${price}" for name, price in SHOP_ITEMS.items()])
response = f"**Available Items:**\n{items_list}\n\nTo buy an item, use `/fun shop <item>`."
await interaction.response.send_message(response)
else:
item_key = item.lower()
if item_key not in SHOP_ITEMS:
await interaction.response.send_message("That item is not available in the shop!")
return
price = SHOP_ITEMS[item_key]
if account["balance"] < price:
await interaction.response.send_message("You don't have enough money to buy that item!")
return
# Deduct the price and add the item to the user's inventory.
account["balance"] -= price
account["inventory"].append(item_key)
await interaction.response.send_message(
f"You bought a {item.title()} for ${price}.\nYour new balance is ${account['balance']}."
)
@fun_group.command(name="gamble", description="Gamble a certain amount of money in a coin flip!")
@app_commands.describe(amount="The amount of money you want to gamble.")
async def gamble(self, interaction: discord.Interaction, amount: int):
if amount <= 0:
await interaction.response.send_message("You must gamble a positive amount!")
return
account = self.get_account(interaction.user.id)
if account["balance"] < amount:
await interaction.response.send_message("You don't have that much money!")
return
# Simple coin flip: win doubles your bet, lose subtracts it.
if random.choice([True, False]):
account["balance"] += amount
await interaction.response.send_message(
f"You won! You earned an extra ${amount}.\nYour new balance is ${account['balance']}."
)
else:
account["balance"] -= amount
await interaction.response.send_message(
f"You lost the gamble and lost ${amount}.\nYour new balance is ${account['balance']}."
)
# The setup function to add this cog and register the command group.
async def setup(bot: commands.Bot):
cog = Fun(bot)
await bot.add_cog(cog)
# Register the entire /fun group to the bot's command tree.
bot.tree.add_command(Fun.fun_group)

161
cogs/serverconfig.py Normal file
View File

@ -0,0 +1,161 @@
import discord
from discord.ext import commands
from discord import app_commands
import os
import json
# Path to the JSON config file
CONFIG_FILE = "/home/server/serverconfig.json"
def load_config() -> dict:
"""Load the server configuration from file.
If the file does not exist or is invalid, create a new empty configuration."""
if not os.path.exists(CONFIG_FILE):
with open(CONFIG_FILE, "w") as f:
json.dump({}, f)
return {}
try:
with open(CONFIG_FILE, "r") as f:
return json.load(f)
except json.JSONDecodeError:
return {}
def save_config(data: dict) -> None:
"""Save the configuration JSON to file."""
with open(CONFIG_FILE, "w") as f:
json.dump(data, f, indent=4)
async def global_disabled_check(interaction: discord.Interaction) -> bool:
"""
Global check for all app (slash) commands.
If the command (except for serverconfig itself) is marked as disabled in this servers config,
send an ephemeral message and prevent execution.
"""
# If interaction comes from a DM, allow it.
if interaction.guild is None:
return True
# Always allow the serverconfig command so admins can change settings.
if interaction.command and interaction.command.name == "serverconfig":
return True
config = load_config()
guild_id = str(interaction.guild.id)
disabled_commands = config.get(guild_id, [])
if interaction.command and interaction.command.name in disabled_commands:
if not interaction.response.is_done():
await interaction.response.send_message(
"This command has been disabled by server admins.", ephemeral=True
)
# Raising a CheckFailure prevents the command from running.
raise app_commands.CheckFailure("Command disabled.")
return True
class ServerConfigCog(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
@app_commands.command(
name="serverconfig",
description="Enable or disable a command in this server."
)
@app_commands.describe(
command="The name of the command to configure",
enabled="Type 'yes' to enable or 'no' to disable."
)
async def serverconfig(
self,
interaction: discord.Interaction,
command: str,
enabled: str
):
# Check if the user has admin permissions.
if not interaction.user.guild_permissions.administrator:
await interaction.response.send_message(
"You do not have permission to use this command.",
ephemeral=True
)
return
# Normalize the enabled flag.
enabled_flag = enabled.lower()
if enabled_flag not in ["yes", "no"]:
await interaction.response.send_message(
"Invalid 'enabled' option. Please use 'yes' or 'no'.",
ephemeral=True
)
return
# Verify that the provided command exists.
found = False
# Check the classic text commands.
for cmd in self.bot.commands:
if cmd.name == command:
found = True
break
# Also check application (slash) commands from the tree.
if not found:
for cmd in self.bot.tree.get_commands():
if cmd.name == command:
found = True
break
if not found:
await interaction.response.send_message(
f"The command '{command}' was not found.",
ephemeral=True
)
return
# Load the configuration.
config = load_config()
guild_id = str(interaction.guild.id)
if guild_id not in config:
config[guild_id] = []
if enabled_flag == "no":
# Add the command to the disabled list if not already present.
if command not in config[guild_id]:
config[guild_id].append(command)
save_config(config)
await interaction.response.send_message(
f"Command '{command}' has been **disabled** in this server.",
ephemeral=True
)
else: # enabled_flag == "yes"
# Remove the command from the disabled list if present.
if command in config[guild_id]:
config[guild_id].remove(command)
save_config(config)
await interaction.response.send_message(
f"Command '{command}' has been **enabled** in this server.",
ephemeral=True
)
@serverconfig.autocomplete("command")
async def command_autocomplete(
self, interaction: discord.Interaction, current: str
) -> list[app_commands.Choice[str]]:
"""
Autocomplete for the 'command' parameter.
It searches both classic and slash commands for matches.
"""
choices = set()
# Get names of text commands.
for cmd in self.bot.commands:
choices.add(cmd.name)
# Get names of app commands.
for cmd in self.bot.tree.get_commands():
choices.add(cmd.name)
# Filter and send at most 25 matching choices.
filtered = [
app_commands.Choice(name=cmd, value=cmd)
for cmd in choices
if current.lower() in cmd.lower()
]
return filtered[:25]
async def setup(bot: commands.Bot):
# Register the global check it will run for every application (slash) command.
bot.tree.add_check(global_disabled_check)
await bot.add_cog(ServerConfigCog(bot))

0
website/.gitkeep Normal file
View File