mirror of
https://github.com/alterware/aw-bot.git
synced 2025-11-18 09:02:07 +00:00
init
This commit is contained in:
47
.github/workflows/docker-publish.yml
vendored
Normal file
47
.github/workflows/docker-publish.yml
vendored
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
name: Build and Push Docker Image
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
tags:
|
||||||
|
- '[0-9]+.[0-9]+.[0-9]+'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
docker:
|
||||||
|
name: Create Docker Image
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.ref_type == 'tag'
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3.4.0
|
||||||
|
|
||||||
|
- name: Log in to DockerHub
|
||||||
|
uses: docker/login-action@v3.2.0
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USER }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- id: meta
|
||||||
|
uses: docker/metadata-action@v5.5.1
|
||||||
|
with:
|
||||||
|
images: |
|
||||||
|
alterware/aw-bot
|
||||||
|
tags: |
|
||||||
|
${{ github.ref_name }}
|
||||||
|
latest
|
||||||
|
|
||||||
|
- name: Build and push Docker image
|
||||||
|
id: build-and-push
|
||||||
|
uses: docker/build-push-action@v6.3.0
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: linux/amd64
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
||||||
32
.github/workflows/lint.yml
vendored
Normal file
32
.github/workflows/lint.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
name: Lint
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
env:
|
||||||
|
PIP_ROOT_USER_ACTION: "ignore"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
name: Lint Python code
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install pip
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y python3-pip
|
||||||
|
|
||||||
|
- name: Upgrade pip
|
||||||
|
run: |
|
||||||
|
python3 -m pip install --upgrade pip
|
||||||
|
pip --version
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
- name: Run flake8
|
||||||
|
run: |
|
||||||
|
flake8 .
|
||||||
13
Dockerfile
Normal file
13
Dockerfile
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
FROM python:alpine
|
||||||
|
|
||||||
|
WORKDIR /aw-bot
|
||||||
|
|
||||||
|
COPY . /aw-bot
|
||||||
|
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
COPY patterns.json /aw-bot
|
||||||
|
|
||||||
|
ENV BOT_TOKEN=""
|
||||||
|
|
||||||
|
CMD ["python", "aw.py"]
|
||||||
2
README.md
Normal file
2
README.md
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# AlterWare's Discord Bot
|
||||||
|
This repo contains the AlterWare Bot's source code. It is written in Python using discord.py
|
||||||
231
aw.py
Normal file
231
aw.py
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
import datetime
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
|
import discord
|
||||||
|
import requests
|
||||||
|
from discord import app_commands
|
||||||
|
from discord.ext import commands, tasks
|
||||||
|
|
||||||
|
GUILD_ID = 1110531063161299074
|
||||||
|
BOT_LOG = 1112049391482703873
|
||||||
|
|
||||||
|
# Define the channel IDs where auto responds are allowed
|
||||||
|
ALLOWED_CHANNELS = [
|
||||||
|
1110531063744303138,
|
||||||
|
1112048063448617142,
|
||||||
|
1145458108190163014,
|
||||||
|
1145456435518525611,
|
||||||
|
1145469136919613551,
|
||||||
|
1145459788151537804,
|
||||||
|
1145469106133401682,
|
||||||
|
1117540484085194833,
|
||||||
|
]
|
||||||
|
GENERAL_CHANNEL = 1110531063744303138
|
||||||
|
|
||||||
|
# Load existing patterns from file
|
||||||
|
try:
|
||||||
|
with open("patterns.json", "r") as f:
|
||||||
|
patterns = json.load(f)
|
||||||
|
except FileNotFoundError:
|
||||||
|
patterns = []
|
||||||
|
|
||||||
|
bot = commands.Bot(command_prefix="!", intents=discord.Intents.all())
|
||||||
|
tree = bot.tree
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_api_data():
|
||||||
|
response = requests.get("https://api.getserve.rs/v1/servers/alterware")
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
async def fetch_game_stats(game: str):
|
||||||
|
url = f"https://api.getserve.rs/v1/servers/alterware/{game}"
|
||||||
|
response = requests.get(url)
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
async def compile_stats():
|
||||||
|
games = ["iw4", "s1", "iw6"]
|
||||||
|
stats_message = "**Stats for all games:**\n"
|
||||||
|
for game in games:
|
||||||
|
data = await fetch_game_stats(game)
|
||||||
|
if data:
|
||||||
|
count_servers = data.get("countServers", "N/A")
|
||||||
|
count_players = data.get("countPlayers", "N/A")
|
||||||
|
stats_message += f"**{game.upper()}:** Total Servers: {count_servers}, Total Players: {count_players}\n" # noqa
|
||||||
|
else:
|
||||||
|
stats_message += f"**{game.upper()}:** Failed to fetch stats.\n"
|
||||||
|
return stats_message
|
||||||
|
|
||||||
|
|
||||||
|
async def perform_search(query: str):
|
||||||
|
data = fetch_api_data()
|
||||||
|
servers = data.get("servers", [])
|
||||||
|
matching_servers = [
|
||||||
|
server
|
||||||
|
for server in servers
|
||||||
|
if query.lower() in server.get("hostnameDisplay", "").lower()
|
||||||
|
or query.lower() in server.get("ip", "").lower()
|
||||||
|
]
|
||||||
|
|
||||||
|
if not matching_servers:
|
||||||
|
return "No servers found matching your query."
|
||||||
|
|
||||||
|
max_results = 5
|
||||||
|
message = f'Top {min(len(matching_servers), max_results)} servers matching "{query}":\n' # noqa
|
||||||
|
for server in matching_servers[:max_results]:
|
||||||
|
message += (
|
||||||
|
f"- **{server['hostnameDisplay']}** | {server['gameDisplay']} | "
|
||||||
|
f"**Gametype**: {server['gametypeDisplay']} | **Map**: {server['mapDisplay']} | " # noqa
|
||||||
|
f"**Players**: {server['realClients']}/{server['maxplayers']}\n"
|
||||||
|
)
|
||||||
|
return message
|
||||||
|
|
||||||
|
|
||||||
|
@tree.command(
|
||||||
|
name="search",
|
||||||
|
description="Search for servers by hostname or IP.",
|
||||||
|
guild=discord.Object(id=GUILD_ID),
|
||||||
|
)
|
||||||
|
async def slash_search(interaction: discord.Interaction, query: str):
|
||||||
|
results = await perform_search(query)
|
||||||
|
await interaction.response.send_message(results)
|
||||||
|
|
||||||
|
|
||||||
|
@app_commands.checks.cooldown(1, 60, key=lambda i: (i.guild_id, i.user.id))
|
||||||
|
@tree.command(
|
||||||
|
name="stats",
|
||||||
|
description="Get stats for a specific game or all games",
|
||||||
|
guild=discord.Object(id=GUILD_ID),
|
||||||
|
)
|
||||||
|
async def stats(
|
||||||
|
interaction: discord.Interaction, game: Literal["iw4", "s1", "iw6", "all"]
|
||||||
|
):
|
||||||
|
if game == "all":
|
||||||
|
stats_message = await compile_stats()
|
||||||
|
else:
|
||||||
|
data = await fetch_game_stats(game)
|
||||||
|
if data:
|
||||||
|
stats_message = f"**Stats for {game.upper()}:**\n"
|
||||||
|
count_servers = data.get("countServers", "N/A")
|
||||||
|
count_players = data.get("countPlayers", "N/A")
|
||||||
|
stats_message += f"Total Servers: {count_servers}\n" # noqa
|
||||||
|
stats_message += f"Total Players: {count_players}\n" # noqa
|
||||||
|
else:
|
||||||
|
stats_message = (
|
||||||
|
"Failed to fetch game stats. Please try again later." # noqa
|
||||||
|
)
|
||||||
|
|
||||||
|
await interaction.response.send_message(stats_message, ephemeral=True)
|
||||||
|
# await interaction.delete_original_response()
|
||||||
|
|
||||||
|
|
||||||
|
async def on_tree_error(
|
||||||
|
interaction: discord.Interaction, error: app_commands.AppCommandError
|
||||||
|
):
|
||||||
|
if isinstance(error, app_commands.CommandOnCooldown):
|
||||||
|
return await interaction.response.send_message(
|
||||||
|
f"Command is currently on cooldown! Try again in **{error.retry_after:.2f}** seconds!" # noqa
|
||||||
|
)
|
||||||
|
elif isinstance(error, app_commands.MissingPermissions):
|
||||||
|
return await interaction.response.send_message(
|
||||||
|
"You are missing permissions to use that"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise error
|
||||||
|
|
||||||
|
|
||||||
|
bot.tree.on_error = on_tree_error
|
||||||
|
|
||||||
|
|
||||||
|
@bot.event
|
||||||
|
async def on_message_delete(message):
|
||||||
|
channel = bot.get_channel(BOT_LOG)
|
||||||
|
if channel:
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="Deleted Message",
|
||||||
|
description="A message was deleted.",
|
||||||
|
color=0xDD2E44,
|
||||||
|
)
|
||||||
|
embed.add_field(
|
||||||
|
name="Author", value=message.author.mention, inline=True
|
||||||
|
) # noqa
|
||||||
|
embed.add_field(
|
||||||
|
name="Channel", value=message.channel.mention, inline=True
|
||||||
|
) # noqa
|
||||||
|
if message.content:
|
||||||
|
embed.add_field(name="Content", value=message.content, inline=False) # noqa
|
||||||
|
embed.set_footer(
|
||||||
|
text=f"Message ID: {message.id} | Author ID: {message.author.id}"
|
||||||
|
)
|
||||||
|
|
||||||
|
await channel.send(embed=embed)
|
||||||
|
|
||||||
|
|
||||||
|
@bot.event
|
||||||
|
async def on_message(message):
|
||||||
|
if message.author == bot.user:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Too many mentions
|
||||||
|
if len(message.mentions) >= 3:
|
||||||
|
await message.delete()
|
||||||
|
member = message.guild.get_member(message.author.id)
|
||||||
|
if member:
|
||||||
|
# Timeout the member for 60 seconds
|
||||||
|
await member.timeout_for(
|
||||||
|
discord.utils.utcnow() + datetime.timedelta(seconds=60)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Auto delete torrent if post in chat.
|
||||||
|
for file in message.attachments:
|
||||||
|
if file.filename.endswith((".torrent", ".TORRENT")):
|
||||||
|
await message.delete()
|
||||||
|
|
||||||
|
# Check if the message is in an allowed channel
|
||||||
|
if message.channel.id not in ALLOWED_CHANNELS:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Check if any of the patterns match the message
|
||||||
|
# print('Checking for patterns...')
|
||||||
|
for pattern in patterns:
|
||||||
|
if re.search(pattern["regex"], message.content, re.IGNORECASE):
|
||||||
|
# print('Checking message content:', message.content, re.IGNORECASE) # noqa
|
||||||
|
# print('Matching pattern regex:', pattern['regex']) # noqa
|
||||||
|
# print('Pattern match:', re.search(pattern['regex'], message.content, re.IGNORECASE)) # noqa
|
||||||
|
response = pattern["response"]
|
||||||
|
await message.channel.send(response)
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
# Update Player Counts from API
|
||||||
|
@tasks.loop(minutes=10)
|
||||||
|
async def update_status():
|
||||||
|
data = fetch_api_data()
|
||||||
|
countPlayers = data.get("countPlayers", 0)
|
||||||
|
countServers = data.get("countServers", 0)
|
||||||
|
activity = discord.Game(
|
||||||
|
name=f"with {countPlayers} players on {countServers} servers"
|
||||||
|
)
|
||||||
|
await bot.change_presence(activity=activity)
|
||||||
|
|
||||||
|
|
||||||
|
@bot.event
|
||||||
|
async def on_ready():
|
||||||
|
print(f"{bot.user.name} has connected to Discord!")
|
||||||
|
await tree.sync(
|
||||||
|
guild=discord.Object(id=GUILD_ID)
|
||||||
|
) # Sync commands for a specific guild.
|
||||||
|
update_status.start()
|
||||||
|
|
||||||
|
|
||||||
|
bot.run(os.getenv("BOT_TOKEN"))
|
||||||
18
patterns.json
Normal file
18
patterns.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"regex": "(?:do\\s+|if\\s+i\\s+don't\\s+)?(?:i|you)?\\s*(?:really\\s+)?(?:need\\s+to|have\\s+to|must|gotta|can)\\s+own\\s+(?:an?\\s+)?(?:official\\s+)?(?:copy\\s+of\\s+)?(?:the\\s+|this\\s+|said\\s+|these\\s+|those\\s+)?(?:cod\\s+)?games?\\s*(?:on\\s+steam|on\\s+alterware)?",
|
||||||
|
"response": "Yes, it is required to own a copy of the game to utilize our clients"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regex": "\bhow can I open the console ingame\b",
|
||||||
|
"response": "Press the tilde key (~) or grave key (`) to enter console.\n https://i.imgur.com/drPaC0f.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regex": "(.*)(HOW|WHERE)(.*)(CHANGE|MODIFY)(.*)(USERNAME|NAME|IGN)(.*)",
|
||||||
|
"response": "``/name WhateverNameYouWant`` on the in-game console.\nYou open the in-game console with the ~ or ` key (underneath ESC). If you're on a 60% keyboard it's Fn+ESC.\nhttps://i.imgur.com/drPaC0f.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"regex": "online profile is invalid",
|
||||||
|
"response": "Close your game, locate your Modern Warfare 2 game folder, and delete the `players` folder"
|
||||||
|
}
|
||||||
|
]
|
||||||
3
requirements.txt
Normal file
3
requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
discord.py
|
||||||
|
flake8
|
||||||
|
requests
|
||||||
Reference in New Issue
Block a user