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