commit
fed84a8c0d
15 changed files with 424 additions and 0 deletions
@ -0,0 +1 @@ |
|||
.env |
@ -0,0 +1,6 @@ |
|||
FROM python:3.10 |
|||
RUN python -m pip install git+https://github.com/Rapptz/discord.py aiohttp |
|||
ENV PYTHONUNBUFFERED 1 |
|||
WORKDIR /app |
|||
COPY ./ ./ |
|||
ENTRYPOINT ["python", "bot.py"] |
@ -0,0 +1,14 @@ |
|||
import discord |
|||
|
|||
class Extension: |
|||
def __init__(self, core): |
|||
@core.tree.command(name = "kick", description = "Кикнуть игрока") |
|||
@discord.app_commands.describe(profile=core.ANY_INPUT, reason="причина") |
|||
async def kick_player( |
|||
interaction: discord.Interaction, |
|||
profile: str, |
|||
reason: str = "" |
|||
): |
|||
steam64 = await core.GetSteam64OfDiscord(interaction.user) |
|||
player = await core.GetPlayer(profile, steam64, False) |
|||
return await interaction.response.send_message(f'{await player.kick(reason)}', ephemeral=False) |
@ -0,0 +1,19 @@ |
|||
import discord |
|||
import aiohttp |
|||
|
|||
class Extension: |
|||
def __init__(self, core): |
|||
@core.tree.command(name = "rcon", description = "Вызвать команду на сервере") |
|||
@discord.app_commands.describe(server="Нужным сервер в формате srv1", command="Команда исполнения") |
|||
async def rcon_command( |
|||
interaction: discord.Interaction, |
|||
server: str, |
|||
command: str |
|||
): |
|||
steam64 = await core.GetSteam64OfDiscord(interaction.user) |
|||
if not server in core.stats.get("servers", {}).keys(): |
|||
return await interaction.response.send_message(f"Сервер с таким индификатором не существует, введи существующий из предложенных:\n{' '.join(core.stats.get('servers', {}).keys())}", ephemeral=False) |
|||
|
|||
async with aiohttp.ClientSession(cookies={"secretkey":core.secret_key, "steam64":steam64}) as session: |
|||
async with session.post(f"{core.backend_url}/api/admin/rcon?srv={server}&command={command}", ssl=False) as response: |
|||
return await interaction.response.send_message(f'{await response.text()}', ephemeral=False) |
@ -0,0 +1,15 @@ |
|||
import discord |
|||
|
|||
class Extension: |
|||
def __init__(self, core): |
|||
@core.tree.command(name = "use", description = "Использовать команду на игрока") |
|||
@discord.app_commands.describe(command="Команда исполнения", profile=core.ANY_INPUT, args="Аргумент если нужен") |
|||
async def rcon_use_command( |
|||
interaction: discord.Interaction, |
|||
command: str, |
|||
profile: str, |
|||
args: str = "" |
|||
): |
|||
steam64 = await core.GetSteam64OfDiscord(interaction.user) |
|||
player = await core.GetPlayer(profile, steam64) |
|||
return await interaction.response.send_message(f'{await player.rcon(command, args)}', ephemeral=False) |
@ -0,0 +1,88 @@ |
|||
import ssl |
|||
import discord |
|||
from discord import app_commands |
|||
import os, sys |
|||
import aiohttp |
|||
from datetime import datetime |
|||
from discord.ext import tasks |
|||
from player import * |
|||
|
|||
#Скрыть сообщение если надо ephemeral=True |
|||
|
|||
|
|||
class NeedDiscordAuthOfSteam(Exception): |
|||
pass |
|||
|
|||
|
|||
class DiscordClient(discord.Client): |
|||
ANY_INPUT = "ссылка на стим | имя игрока | стимид" |
|||
discord2steam_cache = {} |
|||
backend_url = "" |
|||
secret_key = "" |
|||
stats = {} |
|||
show_stats_prev = 0 |
|||
|
|||
def __init__(self, backend_url, secret_key): |
|||
self.backend_url = backend_url |
|||
self.secret_key = secret_key |
|||
################################################### |
|||
super().__init__(intents=discord.Intents.default()) |
|||
self.tree = app_commands.CommandTree(self) |
|||
self.load_extensions(['user_ext', 'admin_ext', 'other_ext']) |
|||
|
|||
def load_extensions(self, extensions_path): |
|||
if type(extensions_path) == str: |
|||
extensions_path = [extensions_path] |
|||
for path in extensions_path: |
|||
print(f"Load extensions from: {path}") |
|||
sys.path.insert(0, path) |
|||
for extension in os.listdir(path): |
|||
extension, ext = os.path.splitext(extension) |
|||
if ext != ".py": |
|||
continue |
|||
print(f"Loading: {extension}") |
|||
__import__(extension).Extension(self) |
|||
sys.path.pop(0) |
|||
|
|||
async def setup_hook(self): |
|||
self.stats = await self.GetStats() |
|||
print("sync tree") |
|||
await self.tree.sync(guild=discord.Object(320241437620830209)) |
|||
await self.tree.sync() |
|||
|
|||
def setup_events(self): |
|||
@self.event |
|||
async def on_ready(): |
|||
print(f'Logged in as {self.user} (ID: {self.user.id})') |
|||
|
|||
async def GetSteam64OfDiscord(self, user, no_cache = False): |
|||
if user.id in self.discord2steam_cache and not no_cache: |
|||
return self.discord2steam_cache[user.id] |
|||
|
|||
async with aiohttp.ClientSession(cookies={"secretkey":self.secret_key}) as session: |
|||
async with session.get(f"{self.backend_url}/api/discord?discord_id={user.id}", ssl=False) as response: |
|||
steamid_response = await response.json() |
|||
if steamid_response != None: |
|||
self.discord2steam_cache[user.id] = steamid_response["steam64"] |
|||
else: |
|||
raise NeedDiscordAuthOfSteam |
|||
|
|||
return self.discord2steam_cache[user.id] |
|||
|
|||
async def GetPlayer(self, profile, requester_steam64, load_profile = True): |
|||
player = Player(profile, requester_steam64, self.stats) |
|||
await player.GetSteamID() |
|||
if load_profile: |
|||
await player.LoadProfile() |
|||
return player |
|||
|
|||
async def GetStats(self): |
|||
async with aiohttp.ClientSession() as session: |
|||
async with session.get(f"{os.getenv('BACKEND_URL')}/api/stats", ssl=False) as response: |
|||
return await response.json() |
|||
|
|||
if __name__ == "__main__": |
|||
DiscordClient( |
|||
os.getenv("BACKEND_URL"), |
|||
os.getenv("BACKEND_SECRETKEY") |
|||
).run(os.getenv("DISCORD_TOKEN")) |
@ -0,0 +1,10 @@ |
|||
services: |
|||
facti13bot_discord_v2: |
|||
build: . |
|||
container_name: facti13bot_discord_v2 |
|||
env_file: |
|||
- .env |
|||
extra_hosts: |
|||
- "tf2.pblr-nyk.pro:192.168.3.3" |
|||
hostname: 'discord' |
|||
restart: unless-stopped |
@ -0,0 +1,6 @@ |
|||
FROM python:3.10 |
|||
RUN python -m pip install git+https://github.com/Rapptz/discord.py aiohttp |
|||
RUN cd /tmp && git clone https://git.pblr-nyk.pro/gsd/Facti13.Bot.Discord.V2 && cd Facti13.Bot.Discord.V2 && cp ./ /app |
|||
ENV PYTHONUNBUFFERED 1 |
|||
WORKDIR /app |
|||
ENTRYPOINT ["python", "bot.py"] |
@ -0,0 +1,6 @@ |
|||
from discord.ext import tasks |
|||
class Extension: |
|||
def __init__(self, core): |
|||
@tasks.loop(seconds=60.0) |
|||
async def stats_loader(core): |
|||
core.stats = await core.GetStats() |
@ -0,0 +1,19 @@ |
|||
from discord.ext import tasks |
|||
import discord |
|||
class Extension: |
|||
def __init__(self, core): |
|||
show_stats_prev = 0 |
|||
@tasks.loop(seconds=3) |
|||
async def show_stats(core): |
|||
if not core.stats['servers'].items(): |
|||
print("Stats not be loaded") |
|||
return |
|||
try: |
|||
server = core.stats['servers'].items()[core.show_stats_prev] |
|||
except: |
|||
show_stats_prev = 0 |
|||
server = core.stats['servers'].items()[core.show_stats_prev] |
|||
|
|||
addr = server['address'].split(":") |
|||
act = discord.Streaming(name = f"{server['name']} - {server['player_count']}", url=f"https://{addr[0]}/connect/{addr[1]}") |
|||
await core.change_presence(activity=act) |
@ -0,0 +1,151 @@ |
|||
import aiohttp, os |
|||
from datetime import datetime |
|||
|
|||
class CannotCastToSteamID(Exception): |
|||
pass |
|||
|
|||
class Player: |
|||
original_request = "" |
|||
requester_steam64 = "" |
|||
current = {} |
|||
steamid = {} |
|||
play_on = {} |
|||
def __init__(self, profile, requester_steam64, stats): |
|||
self.requester_steam64 = requester_steam64 |
|||
self.original_request = profile |
|||
self.stats = stats |
|||
#потом надо будет сделать что профиль принимает все, а не только стим ид |
|||
#api/profile/steam, ток там буква Z поэтому стоит поменять на секрет кей |
|||
pass |
|||
|
|||
async def GetSteamID(self): |
|||
self.steamid = await self.GetSteamIDOfProfile(self.original_request) |
|||
|
|||
async def LoadProfile(self): |
|||
self.current = await self.GetProfile(self.steamid.get("steam64")) |
|||
|
|||
def __str__(self): |
|||
if not self.current: |
|||
return "profile not load from backend, use GetProfile" |
|||
|
|||
message = self.current["steamids"]["community_url"] + "\n" |
|||
if "play_on" in self.current and self.current["play_on"]: |
|||
message += f"Сейчас играет на {self.stats['servers'][self.current['play_on']['server_id']]['name']}\n" |
|||
message += "\n" |
|||
#тут должна быть последняя причина выхода с сервера, но она не релизована в бекенде |
|||
message += "Последняя игра на серверах:\n" |
|||
if "lastplay" in self.current and self.current["lastplay"]: |
|||
for maps in self.current["lastplay"].values(): |
|||
for map_name, last_play in maps.items(): |
|||
message += f"{workshopmap2bsp(map_name)} - {utime2human(last_play)}\n" |
|||
else: |
|||
message += "Не играл у нас\n" |
|||
message += "\n" |
|||
#Далее игровое время бро |
|||
if "gametime" in self.current and self.current["gametime"]: |
|||
message += "Статистика по картам:\n" |
|||
for maps in self.current["gametime"].values(): |
|||
for map_name, play_time in maps.items(): |
|||
message += f"{workshopmap2bsp(map_name)} - {human_TIME(play_time)}\n" |
|||
message += "\n" |
|||
#Далее идут проверка прав |
|||
if "permition" in self.current and self.current["permition"]: |
|||
message += f"Права: {self.current['permition']['status']} назначены {utime2human(self.current['permition']['u_timestamp']) if self.current['permition']['u_timestamp'] != 0 else 'с момента создания'}\n" |
|||
if self.current['permition'].get('amount', 0) and self.current['permition'].get("u_timestamp", 0): |
|||
message += f"Кончаются: {utime2human(self.current['permition']['u_timestamp'] + self.current['permition']['amount'])}\n" |
|||
message += "\n" |
|||
#Далее проверка бана |
|||
if "ban" in self.current and self.current["ban"]: |
|||
message += "ИМЕЕТСЯ БАН\n" |
|||
message += f"Ник: {self.current['ban']['player_name']}\n" |
|||
message += f"Причина: {self.current['ban']['ban_reason']}\n" |
|||
message += f"Время: {utime2human(self.current['ban']['ban_utime'])}\n" |
|||
message += f"Кто забанил: {self.current['ban']['banned_by']} | <@{self.current['ban']['admin_info']['discord_id']}>\n" |
|||
if self.current['ban']['active'] == True: |
|||
if self.current['ban']['ban_length'] == 0: |
|||
message += "Данный бан навсегда!\n" |
|||
else: |
|||
message += f"Дата разбана: {utime2human(self.current['ban']['ban_utime'] + self.current['ban']['ban_length_seconds'])}\n" |
|||
else: |
|||
message += f"Кто разбанил: {'бан снялся со временем' if self.current['ban']['unbanned_by_id'] == 'STEAM_0:0:0' else self.current['ban']['unbanned_by_id']}\n" |
|||
#Не реализованное получение числа скок был в бане |
|||
return message |
|||
|
|||
async def GetSteamIDOfProfile(self, any:str): |
|||
async with aiohttp.ClientSession(cookies={"secretkey":os.getenv("BACKEND_SECRETKEY")}) as session: |
|||
async with session.get(f"{os.getenv('BACKEND_URL')}/api/profile/steam?any={any}", ssl=False) as response: |
|||
response = await response.json() |
|||
if response == None: |
|||
raise CannotCastToSteamID |
|||
return response |
|||
|
|||
async def GetProfile(self, steam64): |
|||
async with aiohttp.ClientSession(cookies={"secretkey":os.getenv("BACKEND_SECRETKEY")}) as session: |
|||
async with session.get(f"{os.getenv('BACKEND_URL')}/api/profile?steam64={steam64}", ssl=False) as response: |
|||
return await response.json() |
|||
|
|||
############### |
|||
#admin commands |
|||
############### |
|||
async def kick(self, reason): |
|||
async with aiohttp.ClientSession(cookies={ |
|||
"secretkey":os.getenv("BACKEND_SECRETKEY"), |
|||
"steam64": self.requester_steam64}) as session: |
|||
async with session.post(f"{os.getenv('BACKEND_URL')}/api/admin/kick?steam64={self.steamid.get('steam64')}", ssl=False) as response: |
|||
result = await response.text() |
|||
if response.status == 200: |
|||
return "Кикнут с серверов" |
|||
if response.status == 404: |
|||
return "Игрок не найден на серверах" |
|||
if response.status == 403: |
|||
return "Это не для тебя и не для таких как ты сделано..." |
|||
return "помогите я обосрался" |
|||
|
|||
async def rcon(self, command, args): |
|||
if not self.current: |
|||
return "добродей дурачек забыл прогрузить профиль" |
|||
|
|||
if self.current.get("play_on", {}): |
|||
server = self.current['play_on']['server_id'] |
|||
player_id = self.current['play_on']['player_id'] |
|||
final_command = f"{command} #{player_id} {args}" |
|||
async with aiohttp.ClientSession(cookies={ |
|||
"secretkey":os.getenv("BACKEND_SECRETKEY"), |
|||
"steam64": self.requester_steam64}) as session: |
|||
async with session.post(f"{os.getenv('BACKEND_URL')}/api/admin/rcon?srv={server}&command={final_command}", ssl=False) as response: |
|||
return await response.text() |
|||
else: |
|||
return "Игрок не играет на серверах" |
|||
|
|||
|
|||
############### |
|||
#user command |
|||
############### |
|||
async def report(self, reason): |
|||
async with aiohttp.ClientSession(cookies={ |
|||
"secretkey":os.getenv("BACKEND_SECRETKEY"), |
|||
"steam64": self.requester_steam64}) as session: |
|||
async with session.post(f"{os.getenv('BACKEND_URL')}/api/profile/current/report?steam64={self.steamid.get('steam64')}&text={reason}", ssl=False) as response: |
|||
result = int(await response.text()) |
|||
if result == 0: |
|||
return "Игрок с таким именем не играет на серверах в данный момент" |
|||
else: |
|||
return f"Падажди, следующий репорт можно отправить только после: {utime2human(result)}" |
|||
|
|||
|
|||
def workshopmap2bsp(map_name): |
|||
return map_name.split('/')[-1:][0].split('.ugc')[0] |
|||
|
|||
def utime2human(utime): |
|||
return datetime.fromtimestamp(utime).strftime('%H:%M:%S %d.%m.%Y') |
|||
|
|||
def human_TIME(seconds): |
|||
m, s = divmod(int(seconds), 60) |
|||
h, m = divmod(m, 60) |
|||
d, h = divmod(h, 24) |
|||
if not d: |
|||
return "%d:%02d:%02d" % (h, m, s) |
|||
elif d < 2: |
|||
return "%d день %d:%02d:%02d" % (d ,h, m, s) |
|||
else: |
|||
return "%d дней %d:%02d:%02d" % (d ,h, m, s) |
@ -0,0 +1,9 @@ |
|||
import discord |
|||
|
|||
class Extension: |
|||
def __init__(self, core): |
|||
@core.tree.command(name = "nyk", description = "Сренькнуть в ответ") |
|||
async def nyk( |
|||
interaction: discord.Interaction |
|||
): |
|||
return await interaction.response.send_message(f'среньк', ephemeral=False) |
@ -0,0 +1,13 @@ |
|||
import discord |
|||
|
|||
class Extension: |
|||
def __init__(self, core): |
|||
@core.tree.command(name = "profile", description = "Проверить профиль") |
|||
@discord.app_commands.describe(profile=core.ANY_INPUT) |
|||
async def check_profile( |
|||
interaction: discord.Interaction, |
|||
profile: str = "" |
|||
): |
|||
steam64 = await core.GetSteam64OfDiscord(interaction.user) |
|||
player = await core.GetPlayer(profile, steam64) if profile else await core.GetPlayer(steam64, steam64) |
|||
return await interaction.response.send_message(f'{player}', ephemeral=False) |
@ -0,0 +1,14 @@ |
|||
import discord |
|||
|
|||
class Extension: |
|||
def __init__(self, core): |
|||
@core.tree.command(name = "report", description = "Пожаловать на игрока с сервера") |
|||
@discord.app_commands.describe(profile=core.ANY_INPUT) |
|||
async def report_player( |
|||
interaction: discord.Interaction, |
|||
profile: str, |
|||
reason: str |
|||
): |
|||
steam64 = await core.GetSteam64OfDiscord(interaction.user) |
|||
player = await core.GetPlayer(profile, steam64, False) |
|||
return await interaction.response.send_message(f'{await player.report(reason)}', ephemeral=False) |
@ -0,0 +1,53 @@ |
|||
import discord |
|||
|
|||
class Extension: |
|||
def __init__(self, core): |
|||
@core.tree.command(name = "servers", description = "Показать статистику с серверов") |
|||
@discord.app_commands.describe(server = "ид сервера в формате srv1") |
|||
async def get_servers( |
|||
interaction: discord.Interaction, |
|||
server: str = "" |
|||
): |
|||
steam64 = await core.GetSteam64OfDiscord(interaction.user) |
|||
embed = discord.Embed() |
|||
|
|||
if server: |
|||
if not server in core.stats.get("servers", {}).keys(): |
|||
return await interaction.response.send_message(f"Сервер с таким индификатором не существует, введи существующий из предложенных:\n{' '.join(core.stats.get('servers', {}).keys())}", ephemeral=False) |
|||
embed.add_field(name=core.stats['servers'][server]['name'], value=str_server(core.stats['servers'][server]), inline=False) |
|||
else: |
|||
servers = [server for server in core.stats["servers"].values() if server['status'] == True and server['player_count'] > 0] |
|||
if servers: |
|||
embed.add_field(name = "Где сейчас играют", value = f'{core.stats["statistic"]["player_now"]} карликов', inline=True) |
|||
for server in servers: |
|||
embed.add_field(name = server["name"], value=str_server(server), inline=False) |
|||
|
|||
servers = [server for server in core.stats["servers"].values() if server['status'] == True and server['player_count'] == 0] |
|||
if servers: |
|||
embed.add_field(name = "Пустующие сервера", value = f"{len(servers)} штук", inline=True) |
|||
for server in servers: |
|||
embed.add_field(name = server["name"], value=str_server(server), inline=False) |
|||
|
|||
#не забудь потом сделать лямбды дурачек |
|||
servers = [server for server in core.stats["servers"].values() if server['status'] == False] |
|||
if servers: |
|||
embed.add_field(name = "Неработающие сервера", value = "пук", inline=True) |
|||
for server in servers: |
|||
embed.add_field(name = server["name"], value=str_server(server), inline=False) |
|||
|
|||
return await interaction.response.send_message(embed=embed, ephemeral=False) |
|||
|
|||
|
|||
def str_server(data): |
|||
message = "" |
|||
addr = data['address'].split(":") |
|||
message += f"https://{addr[0]}/connect/{addr[1]}\n" |
|||
if data['status'] == False: |
|||
return message + "Сервер не отвечает" |
|||
|
|||
message += f"Карта: {data['map']}\n" |
|||
message += f"Игроков: {data['player_count']}/{data['max_players']}\n" |
|||
message += "\n" |
|||
for player in data['players']: |
|||
message += f"{player['duration']:7} | {player['score']:3} | {player['name']}\n" |
|||
return message |
Loading…
Reference in new issue