This commit is contained in:
SowinskiBraeden committed 2024-11-16 20:48:24 -08:00
commit 14d4ed5e4a
9 files changed
+380

No files matched your search

+11
View File
@@ -0,0 +1,11 @@
# Discord
DISCORD_TOKEN=
DISCORD_GUILD=
ADMIN_ROLE=
WEBHOOK=
# SFTP
SFTP_HOST=
SFTP_PORT=
SFTP_USERNAME=
SFTP_PASSWORD=
+6
View File
@@ -0,0 +1,6 @@
# Pycache
/__pycache__/*
*/__pycache__/*
# Environment variables
.env
+33
View File
@@ -0,0 +1,33 @@
from discord.ext import commands
from discord import app_commands
from discord import Interaction
class Event(app_commands.Group):
def __init__(self, bot: commands.Bot, name: str, description: str):
super().__init__(name=name, description=description)
self.bot = bot
@app_commands.command(name="start", description="Start an event leaderboard")
async def start(self, interaction: Interaction) -> None:
if self.bot._running: await interaction.response.send_message("Event already started")
self.bot.scrape_file.start()
self.bot._running = True
await interaction.response.send_message("Starting new event leaderboard...")
@app_commands.command(name="stop", description="Stop an event leaderboard")
async def stop(self, interaction: Interaction) -> None:
if not self.bot._running: await interaction.response.send_message("Event already stopped")
self.bot.scrape_file.stop()
# Reset trackers
self.bot._running = False
self.bot.log_file = ""
self.bot.log_index = -1
self.bot.players = []
self.bot.gamertags = []
self.bot.message_id = ""
await interaction.response.send_message("Stopping event leaderboard...")
async def setup(bot: commands.Bot) -> None:
bot.tree.add_command(Event(bot, name="event", description="start or stop an event leaderboard"))
+19
View File
@@ -0,0 +1,19 @@
#!/usr/bin/env python
import os
from dotenv import load_dotenv
load_dotenv()
class Config:
TOKEN: str = os.getenv("DISCORD_TOKEN")
GUILD: str = os.getenv("DISCORD_GUILD")
ADMIN: str = os.getenv("ADMIN_ROLE")
WEBOOK: str = os.getenv("WEBHOOK")
# MONGO_URI: str = os.getenv("MONGO_URI")
# MONGO_DBO: str = os.getenv("MONGO_DBO")
SFTP_HOST: str = os.getenv("SFTP_HOST")
SFTP_PORT: int = int(os.getenv("SFTP_PORT"))
SFTP_USERNAME: str = os.getenv("SFTP_USERNAME")
SFTP_PASSWORD: str = os.getenv("SFTP_PASSWORD")
+122
View File
@@ -0,0 +1,122 @@
#!/usr/bin/env python
from re import match
from paramiko import SFTPClient
from datetime import datetime
from typing import Tuple
# Regex templates
class Templates:
Gamertag: str = r'(?P<time>.*) DEFAULT : BattlEye Server: Adding player identity=(?P<_>.*), name=\'(?P<gamertag>.*)\''
IpAddress: str = r'(?P<time>.*) DEFAULT : BattlEye Server: \'Player #(?P<_>.*) (?P<gamertag>.*) \((?P<ip_addr>.*):(?P<port>.*)\) connected\''
PlayerID: str = r'(?P<time>.*) NETWORK : ### Updating player: PlayerId=(?P<_>.*), Name=(?P<gamertag>.*), IdentityId=(?P<player_guid>.*)'
BattlEyeGUI: str = r'(?P<time>.*) DEFAULT : BattlEye Server: \'Player #(?P<_>.*) (?P<gamertag>.*) - BE GUID: (?P<battleye_guid>.*)\''
Admin: str = r'(?P<time>.*) NETWORK : Player \'(?P<gamertag>.*)\' signed in as server admin.'
Kill: str = r'(?P<time>.*) SCRIPT : ServerAdminTools \| Event serveradmintools_player_killed \| player: (?P<victim_gamertag>.*), instigator: (?P<killer_gamertag>.*), friendly: (?P<friendly>.*)'
# Function to process new logs in an array from a given starting index
def readLogFromIndex(
startingIndex: int,
logs: list[str],
identities: list[dict],
gamertags: list[str]
) -> Tuple[list[dict], list[str]]: # Return updated identities
# Check each line for data to extract and load into json
for i in range(startingIndex, len(logs)):
gt_data = match(Templates.Gamertag, logs[i])
identity_data = match(Templates.PlayerID, logs[i])
admin_data = match(Templates.Admin, logs[i])
kill_data = match(Templates.Kill, logs[i])
# Gather in game player GUID
if identity_data is not None:
for identity in identities:
if identity["gamertag"] == identity_data.group("gamertag"):
identities[identities.index(identity)]["player_guid"] = identity_data.group("player_guid")
continue
# Has player logged into admin/gamemaster
if admin_data is not None:
for identity in identities:
if identity["gamertag"] == admin_data.group("gamertag"):
identities[identities.index(identity)]["admin"] = True
continue
# Contribute to playerts KDR
if kill_data is not None:
if kill_data.group("friendly") == "true": continue # Ignore friendly kills
if kill_data.group("killer_gamertag") == kill_data.group("victim_gamertag"): continue # Ignore suicide
for identity in identities:
if identity["gamertag"] == kill_data.group("killer_gamertag"):
index = identities.index(identity)
identities[index]["kills"] += 1
identities[index]["killstreak"] += 1
identities[index]["deathstreak"] = 0
identities[index]["bestKillstreak"] = identities[index]["killstreak"] if identities[index]["killstreak"] > identities[index]["bestKillstreak"] else identities[index]["bestKillstreak"]
identities[index]["KDR"] = round(identities[index]["kills"] / (1 if identities[index]["deaths"] == 0 else identities[index]["deaths"]), 2)
pass
if identity["gamertag"] == kill_data.group("victim_gamertag"):
index = identities.index(identity)
identities[index]["deaths"] += 1
identities[index]["deathstreak"] += 1
identities[index]["killstreak"] = 0
identities[index]["worstDeathstreak"] = identities[index]["deathstreak"] if identities[index]["deathstreak"] > identities[index]["worstDeathstreak"] else identities[index]["worstDeathstreak"]
identities[index]["KDR"] = round(identities[index]["kills"] / (1 if identities[index]["deaths"] == 0 else identities[index]["deaths"]), 2)
pass
continue
# Generate new player data or update connections/IP info
if gt_data is not None:
ip_data = match(Templates.IpAddress, logs[i+1])
if gt_data.group("gamertag") in gamertags:
for identity in identities:
if identity["gamertag"] == gt_data.group("gamertag"):
index = identities.index(identity)
identities[index]["connections"] += 1
identities[index]["ip"] = ip_data.group("ip_addr")
identities[index]["port"] = ip_data.group("port")
else:
be_data = match(Templates.BattlEyeGUI, logs[i+3])
identities.append({
"gamertag": gt_data.group("gamertag"),
"ip": ip_data.group("ip_addr"),
"port": ip_data.group("port"),
"player_guid": None,
"battleye_guid": be_data.group("battleye_guid"),
"connections": 1,
"kills": 0,
"deaths": 0,
"KDR": 0,
"killstreak": 0,
"bestKillstreak": 0,
"deathstreak": 0,
"worstDeathstreak": 0,
"admin": False
})
gamertags.append(gt_data.group("gamertag"))
continue
return identities, gamertags
def getLatestDir(sftp: SFTPClient, remote_path: str) -> str:
log_directory: str = ""
sub_directories: list[str] = sftp.listdir(remote_path)
base_datetime: datetime = datetime(2000, 1, 1)
# Read sub-directories to get latest modified directory
for directory in sub_directories:
date_time_modified = str(sftp.lstat(f"{remote_path}/{directory}"))
date_time_modified = date_time_modified[len(date_time_modified) - 14:][:-2]
file_datetime = datetime.strptime(date_time_modified, '%y %b %H:%M')
if file_datetime > base_datetime:
base_datetime = file_datetime
log_directory = directory
return log_directory
def scrape(bot) -> None:
pass
+22
View File
@@ -0,0 +1,22 @@
#!/usr/bin/env python
from paramiko import SSHClient, AutoAddPolicy, SFTPClient
from typing import Tuple
from config import Config
def createSSHClient(config: Config) -> Tuple[SFTPClient, SSHClient]:
# Create SSH client
ssh_client = SSHClient()
ssh_client.set_missing_host_key_policy(AutoAddPolicy)
# Connect to SFTP server && create SFTP session
ssh_client.connect(
hostname = config.SFTP_HOST,
port = config.SFTP_PORT,
username = config.SFTP_USERNAME,
password = config.SFTP_PASSWORD,
allow_agent = False,
look_for_keys = False,
disabled_algorithms = { 'pubkeys': ['rsa-sha2-256', 'rsa-sha2-512'] }
)
sftp = ssh_client.open_sftp()
return sftp, ssh_client
+16
View File
@@ -0,0 +1,16 @@
#!/usr/bin/env python
from discord import Intents
import asyncio
from src.ReforgerStatsBot import ReforgerStats
from config import Config
intents = Intents.default()
intents.message_content = True
bot = ReforgerStats(Config(), "!", intents)
async def main() -> None:
async with bot:
await bot.start(bot.config.TOKEN)
asyncio.run(main())
+102
View File
@@ -0,0 +1,102 @@
#!/usr/bin/env python
from paramiko import SSHClient, SFTPClient, SFTPFile
from discord import Intents
from discord.ext import commands
from discord.ext import tasks
from discord import Object
from os import listdir, getcwd
import requests
from handlers.sftp import createSSHClient
from handlers.logs import getLatestDir, readLogFromIndex
from config import Config
class ReforgerStats(commands.Bot):
def __init__(self, config: Config, prefix: str, intents: Intents):
super().__init__(command_prefix=prefix, intents=intents)
self.config: Config = config
self._running: bool = False
self.log_dir: str = ""
self.log_index: int = -1
self.message_id: str = ""
self.players: list[dict] = []
self.gamertags: list[str] = []
self.ssh: SSHClient = None
self.sftp: SFTPClient = None
async def on_ready(self: commands.Bot) -> None:
print(f"Logged in as {self.user}")
await self.load_cogs()
# self.scrape_file.start()
async def load_cogs(self: commands.Bot):
for filename in listdir(f"{getcwd()}/cogs"):
if filename.endswith('.py'):
cog = filename[:-3]
try:
await self.load_extension(f"cogs.{cog}")
except Exception as e:
exception = f"{type(e).__name__}: {e}"
print(exception)
try:
guild = Object(id=self.config.GUILD)
self.tree.copy_global_to(guild=guild)
synced = await self.tree.sync(guild=guild)
print(f"[{guild.id}] Synced {len(synced)} command(s)")
except Exception as e:
exception = f"{type(e).__name__}: {e}"
print(exception)
async def createLeaderboardEmbed(players: list[dict]) -> dict:
sorted_players = sorted(players, key=lambda a: a["kills"], reverse=True)
description = ""
rank = 1
for player in sorted_players:
description += f"> **{rank}.** {player['gamertag']}\n> ***Kills*** {player['kills']}\n> ***Deaths*** {player['deaths']}\n\n"
rank += 1
return {
"embeds": [{
"title": "Leaderboard",
"description": description
}]
}
async def webhookSend(self: commands.Bot, content: dict) -> str | None:
params = { "wait": True }
resp = await requests.post(f"{self.config.WEBHOOK}", json=content, params=params)
return None if resp.status_code != 200 else resp.json()["id"]
async def webhookDeleteMessage(self) -> None:
if self.message_id == "": return
requests.delete(f"{self.config.WEBHOOK}/messages/{self.message_id}")
async def webhookUpdateMessage(self, content: dict) -> None:
if self.message_id == "": return
requests.patch(f"{self.config.WEBHOOK}/messages/{self.message_id}", json=content).status_code
@tasks.loop(seconds=60)
async def scrape_file(self) -> None:
# Create SSH && SFTP sessions if needed
if self.ssh is None or self.sftp is None: self.sftp, self.ssh = createSSHClient(self.config)
remote_path = "/profile/logs"
if self.log_dir == "": self.log_dir = getLatestDir(self.sftp, remote_path)
# Read data
file: SFTPFile = self.sftp.open(f"{remote_path}/{self.log_dir}/console.log")
lines: list[str] = file.readlines()
if self.log_index == -1: self.log_index = len(lines) - 1
self.players, self.gamertags = readLogFromIndex(self.log_index, lines, self.players, self.gamertags)
self.log_index = len(lines) - 1
file.close()
# Send leaderboard embeds via webhook
content: dict = self.createLeaderboardEmbed(self.players)
if self.message_id == "": self.webhookSend(content)
else: self.webhookUpdateMessage(content)
# Close SSH && SFTP sessions and file
self.sftp.close()
self.ssh.close()
+49
View File
@@ -0,0 +1,49 @@
import requests
import time
webhook = "https://discord.com/api/webhooks/1307501649119543398/N-SWGPSTuvaPEk_u9v7WdMJqKASmk0o8EzvjTFDC_1mqDjqWGybSOVGU1iVDZ-LmBsNJ"
identities = [
{"gamertag": "what", "kills": 19, "deaths": 6},
{"gamertag": "mcdazzzled", "kills": 52, "deaths": 11},
{"gamertag": "erebus", "kills": 24, "deaths": 14}
]
def createEmbed(players: list[dict]) -> dict:
sorted_players = sorted(players, key=lambda a: a["kills"], reverse=True)
description = ""
rank = 1
for player in sorted_players:
description += f"> **{rank}.** {player['gamertag']}\n> ***Kills*** {player['kills']}\n> ***Deaths*** {player['deaths']}\n\n"
rank += 1
return {
"embeds": [{
"title": "Leaderboard",
"description": description
}]
}
def send(content: dict) -> str | None:
params = { "wait": True }
resp = requests.post(f"{webhook}", json=content, params=params)
return None if resp.status_code != 200 else resp.json()["id"]
def delete(id: str) -> int:
return requests.delete(f"{webhook}/messages/{id}")
def update(content: dict, id: str) -> int:
resp = requests.patch(f"{webhook}/messages/{id}", json=content)
return resp.status_code
content = createEmbed(identities)
message_id = send(content)
if message_id is None:
print("failed")
exit()
print(identities[2])
identities[0]["kills"] = 30
time.sleep(4)
content = createEmbed(identities)
code = update(content, message_id)
print(code)