mirror of
https://github.com/alterware/aw-bot.git
synced 2025-10-26 14:15:54 +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