Browse Source

first init

huy
gsd 2 years ago
commit
fed84a8c0d
  1. 1
      .gitignore
  2. 6
      Dockerfile
  3. 14
      admin_ext/kick.py
  4. 19
      admin_ext/rcon.py
  5. 15
      admin_ext/use.py
  6. 88
      bot.py
  7. 10
      docker-compose.yaml
  8. 6
      git_build/Dockerfile
  9. 6
      other_ext/stats_loader.py
  10. 19
      other_ext/stats_presence.py
  11. 151
      player.py
  12. 9
      user_ext/nyk.py
  13. 13
      user_ext/profile.py
  14. 14
      user_ext/report.py
  15. 53
      user_ext/servers.py

1
.gitignore

@ -0,0 +1 @@
.env

6
Dockerfile

@ -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"]

14
admin_ext/kick.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)

19
admin_ext/rcon.py

@ -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)

15
admin_ext/use.py

@ -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)

88
bot.py

@ -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"))

10
docker-compose.yaml

@ -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

6
git_build/Dockerfile

@ -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"]

6
other_ext/stats_loader.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()

19
other_ext/stats_presence.py

@ -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)

151
player.py

@ -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)

9
user_ext/nyk.py

@ -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)

13
user_ext/profile.py

@ -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)

14
user_ext/report.py

@ -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)

53
user_ext/servers.py

@ -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…
Cancel
Save