Compare commits

...

33 Commits

Author SHA1 Message Date
e03066341a log on resetting in debug mode 2022-04-03 14:37:15 +02:00
206bafd302 fixed typo 2022-04-03 14:33:49 +02:00
6614accb1d removed starting update message 2022-04-03 08:50:19 +02:00
6becf331e0 implemented start command 2022-04-03 08:42:45 +02:00
bf4cabb122 perms in service script 2022-04-03 08:31:03 +02:00
58416ab8d3 fixed folder path 2022-04-03 08:17:38 +02:00
8daf6fb405 chmod +x permissions for executables 2022-04-03 08:16:57 +02:00
9ac6d5ecb1 fixed service name in systemctl call 2022-04-02 22:13:11 +02:00
88c4bd1992 fixed restart communicate 2022-04-02 22:10:09 +02:00
e506424df3 fixed path 2022-04-02 22:09:03 +02:00
b9f7320da8 de-sudo-d run.sh 2022-04-02 22:06:38 +02:00
5824486f6e testing push 2022-04-02 21:46:26 +02:00
ef46afd052 empty stop command 2022-04-02 21:41:14 +02:00
e4c3ccbf97 fixed permission error 2022-04-02 21:39:02 +02:00
a01a46e20d update test 2022-04-02 21:31:55 +02:00
33301ccfb2 fixed ids 2022-04-02 21:15:16 +02:00
013edeaa66 refactored settings 2022-04-02 21:13:13 +02:00
0a26310a2f license 2022-04-02 21:07:11 +02:00
f6cfdc6f28 git update 2022-04-02 21:04:40 +02:00
39ee845ce1 moved status command 2022-04-02 21:04:16 +02:00
42904399e9 empty start stops 2022-04-02 21:03:31 +02:00
3c835bcf38 systemd settings 2022-04-02 21:03:12 +02:00
e1bb77c073 created bash executable for starting 2022-04-02 21:02:52 +02:00
6cf08376da simplified run.py 2022-04-02 21:02:28 +02:00
71a590b9a6 moved presence 2022-04-02 21:02:11 +02:00
547a4bfed6 simple systemctl service script 2022-04-02 21:01:30 +02:00
e8c6125dce new configs 2022-04-02 21:01:08 +02:00
3058754821 new client with advanced shell executer method 2022-04-02 21:00:51 +02:00
ef3ce4dec6 deleted old file 2022-04-02 21:00:17 +02:00
670cec5335 admin extension with update and restart command 2022-04-02 21:00:06 +02:00
33a0340c3c filled with local imports 2022-04-02 20:59:24 +02:00
9a730f7b2b moved client 2022-04-01 13:56:37 +02:00
e50961bfe0 created empty files 2022-04-01 13:52:18 +02:00
16 changed files with 593 additions and 387 deletions

View File

@@ -1,3 +1,3 @@
# mcserver-discordbot # mcserver-discordbot
Currenlty in development. Readme will follow soon. Currently in development. Readme will follow soon.

View File

@@ -1,381 +0,0 @@
import pprint
from abc import ABC
import asyncio
from configparser import ConfigParser
from datetime import datetime
import logging
import os
import random
import re
import time
from typing import Any
import discord
from discord.commands import slash_command, SlashCommandGroup
from discord.ext import tasks
import mcstatus
from mcstatus import JavaServer
CONFIG_PATH = 'config'
class Configs(object):
"""< object >
This contains all configs and settings defined in config.ini and settings.ini
"""
def __init__(self):
self.start_time = datetime.now()
path_list = re.split('/| \\\\', CONFIG_PATH)
config = ConfigParser(allow_no_value=True)
config.read(f'{os.path.join(*path_list, "") or ""}config.ini')
self.__login_token = config.get('discord', 'token')
self.server_address = config.get('mcserver', 'server_address')
self.server_port = config.getint('mcserver', 'server_port', fallback=25565)
self.admin_ids = config.get('perms', 'admin_ids')
if self.admin_ids:
self.admin_ids = [int(admin_id) for admin_id in self.admin_ids.split(' ')]
self.role_ids = config.get('perms', 'role_ids')
if self.admin_ids:
self.role_ids = [int(admin_id) for admin_id in self.admin_ids.split(' ')]
self.executable_commands = {
'start': config.getboolean('perms', 'start', fallback=True),
'stop': config.getboolean('perms', 'stop', fallback=False),
'status': config.getboolean('perms', 'status', fallback=True),
'dev_commands': config.getboolean('perms', 'false', fallback=False),
}
config.read(f'{os.path.join(*path_list, "") or ""}settings.ini')
self.log_path = config.get('logging', 'path', fallback=os.path.join('logs', ''))
self.log_level = config.getint('logging', 'level', fallback=logging.INFO)
self.retry_in_seconds = config.getint('presence', 'retry_in_seconds', fallback=15)
self.server_start_timout = config.getint('presence', 'server_start_timout', fallback=300)
self.debug_guilds = config.get('debug', 'debug_guilds', fallback=None)
if self.debug_guilds:
self.debug_guilds = [int(guild_id) for guild_id in self.debug_guilds.split(' ')]
self.intents = discord.Intents.none()
self.mc_flags = discord.MemberCacheFlags.from_intents(self.intents)
@property
def auth_token(self) -> str:
"""< property >
This ensures the authentication token can only be used once.
After that it gets deleted for protection reasons
:return: OAuth Token
"""
try:
token = self.__login_token
del self.__login_token
except AttributeError:
raise Exception('OAuth token has already been used once')
else:
return token
class BotClient(discord.Bot, ABC):
"""< discord.Bot >
The bot class
"""
def __init__(self):
self.__config = Configs()
self.logger = self.__setup_logger(self.__config.log_level)
super(BotClient, self).__init__(
# default presence
activity=discord.Game('Beep Boop! Loading...'),
status=discord.Status.idle,
# debug
debug_guilds=self.config.debug_guilds,
)
self.is_server_starting = False
self.last_start = time.time()
self.mc_server = JavaServer(
self.__config.server_address,
self.__config.server_port
)
self.presence_manager = Presence(self)
# extensions
self.add_cog(Status(self))
self.add_cog(Developer(self))
def run(self, *args: Any, **kwargs: Any) -> None:
"""< function >
Starts the bot and automatically gets the configured token.
"""
super(BotClient, self).run(self.__config.auth_token)
@property
def config(self) -> Configs:
"""< property >
The default config should not be changeable even on runtime.
This ensures its read-only.
:return: Butter default config
"""
return self.__config
@property
def color(self) -> int:
"""< property >
Depending on the setting set in ButterConfig either
the bot's default color gets returned or a random
always bright color.
:return: hex-Color
"""
colors: list[int] = [0, 255, random.randint(0, 255)]
random.shuffle(colors)
return int('0x%02x%02x%02x' % tuple(colors), 16)
def __setup_logger(self, level: int = logging.INFO) -> logging.Logger:
"""< function >
Basic logging abilities
"""
path_list = re.split('/| \\\\', self.__config.log_path)
for i, folder in enumerate(path_list):
# filtering empty strings (e.g. for using source folder)
if not folder:
continue
try:
# creates folders
os.mkdir(os.path.join(*path_list[:i + 1]))
except FileExistsError:
continue
logger = logging.getLogger('discord')
logger.setLevel(level)
formatter = logging.Formatter(fmt='[%(asctime)s] - %(levelname)s: %(name)s: %(message)s')
file_handler = logging.FileHandler(filename=f'{os.path.join(*path_list, "") or ""}discord.log',
encoding='utf-8', mode='w')
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
console_handler = logging.StreamHandler()
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)
return logger
async def on_ready(self) -> None:
"""< coroutine >
Logs when the bot is online.
"""
self.logger.info('Bot successfully started')
class Presence(object):
"""<object>
A class for simply managing the bot's presence.
"""
def __init__(self, bot: BotClient):
self.bot = bot
self.mc_server = self.bot.mc_server
self.retry_in_seconds = self.bot.config.retry_in_seconds
self.__server_online_presence.start()
@tasks.loop()
async def __server_online_presence(self):
_time = time.time()
try:
self.bot.logger.debug('Getting server information')
status = await self.mc_server.async_status()
self.bot.is_server_starting = False
await self.bot.change_presence(
activity=discord.Game(
name=f'with {status.players.online} player{"s" if status.players.online != 1 else ""}',
),
status=discord.Status.online
)
await asyncio.sleep(40)
await self.bot.change_presence(
activity=discord.Activity(
type=discord.ActivityType.watching,
name=self.bot.config.server_address,
)
)
await asyncio.sleep(20)
except asyncio.TimeoutError:
if self.bot.is_server_starting and self.bot.last_start + self.bot.config.server_start_timout < time.time():
self.bot.is_server_starting = False
# ToDo: better presence
await self.bot.change_presence(
activity=discord.Activity(
type=discord.ActivityType.watching,
name=f'{self.bot.config.server_address} starting' if self.bot.is_server_starting else
f'offline: [self.server.address]',
),
status=discord.Status.idle
)
# abs -> simple fix if for any reason the sleep_time is negative
sleep_time = abs(_time - time.time() + self.retry_in_seconds)
self.bot.logger.debug(f'Server offline, retrying in {round(sleep_time, 4)} seconds')
await asyncio.sleep(sleep_time)
@__server_online_presence.before_loop
async def __before_status(self) -> None:
"""< coroutine >
Waits until the bot has fully started in case the server
is already online
"""
await self.bot.wait_until_ready()
self.bot.logger.info('presence loaded')
class Developer(discord.Cog):
"""< discord.Cog >
Some developer tools for managing the bot.
"""
def __init__(self, bot: BotClient):
self.bot = bot
async def __permission_granter(self, *path: str) -> int:
process = await asyncio.create_subprocess_shell(
cmd=f'sudo chmod +x {os.path.join(os.getcwd(), *path)}',
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
stdout, stderr = await process.communicate()
self.bot.logger.info(f'Finished bot update with exit code {process.returncode}')
if process.returncode:
self.bot.logger.error(f'stderr:\n{stderr.decode()}')
else:
self.bot.logger.info(f'stdout:\n{stdout.decode()}')
return process.returncode
__dev_group = SlashCommandGroup(name='dev', description='Developer settings')
@__dev_group.command(name='update')
async def __update_bot(self, ctx: discord.ApplicationContext):
response = await ctx.respond('starting update...')
if await self.__permission_granter('scripts', 'update.sh') == 0:
print('okay')
else:
await ctx.respond('Failed to update. Check the log files for further information')
class Status(discord.Cog):
"""< discord.Cog >
An extension to display servers status
"""
def __init__(self, bot: BotClient):
self.bot = bot
self.mc_server = self.bot.mc_server
@slash_command(name='info')
async def __show_server_info(self, ctx: discord.ApplicationContext) -> None:
print()
"""
.add_field(
name='Version',
value="\n".join(re.split(", | ", status.version.name)),
)
"""
@slash_command(name='status')
async def __show_status(self, ctx: discord.ApplicationContext) -> None:
print('exec')
await ctx.defer()
try:
status: mcstatus.pinger.PingResponse = await self.mc_server.async_status()
pprint.pprint(vars(status))
query = await self.mc_server.async_query()
pprint.pprint(vars(query))
await ctx.respond(
embed=discord.Embed(
title=f'Minecraft server status!',
description=f'(**{self.bot.config.server_address}** on port {self.bot.config.server_port})',
colour=self.bot.color,
timestamp=datetime.now()
).add_field(
name='Server Ping',
value=f'**﹂**``{round(status.latency, 2)} ms``',
# value=f'**⌊** **🏓 Pong!** with **``{round(status.latency, 2)}``** ms'
).add_field(
name='Players online',
value=f'**﹂** ``{status.players.online} players``',
)
)
except asyncio.TimeoutError or OSError:
# ToDo: Check os Error on booting
# ToDo: add is starting and create embed
await ctx.respond(embed=discord.Embed(
title=f'Server offline',
description=f'(**{self.bot.config.server_address}** on port {self.bot.config.server_port})',
colour=self.bot.color,
timestamp=datetime.now()
)
)
class StartStop(discord.Cog):
"""< discord.Cog >
"""
if __name__ == '__main__':
BotClient().run()

View File

@@ -1,3 +1,7 @@
# Project is under GNU GENERAL PUBLIC LICENSE 3.0
#
# 2022, created by Specoolazius
# These are the settings you need to configure. # These are the settings you need to configure.
# Rename this file to "config.ini" # Rename this file to "config.ini"
# remove ; to modify further options # remove ; to modify further options

View File

@@ -1,3 +1,7 @@
# Project is under GNU GENERAL PUBLIC LICENSE 3.0
#
# 2022, created by Specoolazius
# You can change setting in here if you wish but it works # You can change setting in here if you wish but it works
# fine as it is configured by default # fine as it is configured by default
# remove ; to modify options # remove ; to modify options
@@ -18,4 +22,7 @@
# the better option is to wait up to an hour (discords regulation) and let the bot # the better option is to wait up to an hour (discords regulation) and let the bot
# create the slash commands globally # create the slash commands globally
# enter multiple guild ids by separating them with space # enter multiple guild ids by separating them with space
debug_guilds = 848137923101982741 958692739065720832 418447236008116228 ; debug_guilds =
[systemd]
; service_name = mc-status-bot

View File

@@ -0,0 +1,9 @@
"""
Project is under GNU GENERAL PUBLIC LICENSE 3.0
2022, created by Specoolazius
"""
from .admin import Admin
from .startstop import StartStop
from .status import Status

46
bot/extensions/admin.py Normal file
View File

@@ -0,0 +1,46 @@
"""
Project is under GNU GENERAL PUBLIC LICENSE 3.0
2022, created by Specoolazius
"""
import asyncio
import os
import discord
from discord.commands import SlashCommandGroup
from libs import Client
class Admin(discord.Cog):
"""< discord.Cog >
Some developer tools for managing the bot.
"""
def __init__(self, bot: Client):
self.bot = bot
__dev_group = SlashCommandGroup(name='dev', description='Developer settings')
@__dev_group.command(name='update')
async def __update_bot(self, ctx: discord.ApplicationContext) -> None:
await ctx.defer()
if 0 == await self.bot.execute_shell('update.sh'):
await ctx.respond('Updated bot from https://github.com/Specoolazius/mcserver-discordbot\n'
'You may need to restart the bot')
else:
await ctx.respond(f'Failed to update bot. Check {os.path.join(self.bot.config.log_path, "discord.log")} '
f'for more detailed information')
@__dev_group.command(name='restart')
async def __restart_service(self, ctx: discord.ApplicationContext) -> None:
await ctx.respond('attempting restart...')
self.bot.logger.info('Restarting bot...')
process = await asyncio.create_subprocess_shell(cmd=f'sudo systemctl restart {self.bot.config.service_name}')
# await process.communicate()

View File

@@ -0,0 +1,48 @@
"""
Project is under GNU GENERAL PUBLIC LICENSE 3.0
2022, created by Specoolazius
"""
import os.path
import discord
from discord.commands import slash_command
from libs import Client
class StartStop(discord.Cog):
"""< discord.Cog >
Extension for starting and stopping the Minecraft server.
"""
def __init__(self, bot: Client):
self.bot = bot
@slash_command(name='start')
async def __execute_start(self, ctx: discord.ApplicationContext) -> None:
await ctx.defer()
self.bot.is_server_starting = True
try:
returncode = await self.bot.execute_shell('start.sh')
self.bot.logger.info(f'Executed start.sh with exit code {returncode}')
except Exception as e:
self.bot.logger.error(
f'Failed to run start.sh\n'
f'Error: {e}'
)
await ctx.respond(
f'Failed to execute start.sh\n'
f'Check {os.path.join(self.bot.config.log_path, "discord.log")} for more detailed information'
)
else:
await ctx.respond('Server is starting')
@slash_command(name='stop')
async def __execute_stop(self, ctx: discord.ApplicationContext) -> None:
await ctx.defer()
await ctx.respond('Not implemented yet')

75
bot/extensions/status.py Normal file
View File

@@ -0,0 +1,75 @@
"""
Project is under GNU GENERAL PUBLIC LICENSE 3.0
2022, created by Specoolazius
"""
import asyncio
from datetime import datetime
import pprint
import discord
from discord.commands import slash_command
import mcstatus.pinger
from libs import Client
class Status(discord.Cog):
"""< discord.Cog >
An extension to display servers status
"""
def __init__(self, bot: Client):
self.bot = bot
self.mc_server = self.bot.mc_server
@slash_command(name='info')
async def __show_server_info(self, ctx: discord.ApplicationContext) -> None:
print()
"""
.add_field(
name='Version',
value="\n".join(re.split(", | ", status.version.name)),
)
"""
@slash_command(name='status')
async def __show_status(self, ctx: discord.ApplicationContext) -> None:
print('exec')
await ctx.defer()
try:
status: mcstatus.pinger.PingResponse = await self.mc_server.async_status()
pprint.pprint(vars(status))
query = await self.mc_server.async_query()
pprint.pprint(vars(query))
await ctx.respond(
embed=discord.Embed(
title=f'Minecraft server status!',
description=f'(**{self.bot.config.server_address}** on port {self.bot.config.server_port})',
colour=self.bot.color,
timestamp=datetime.now()
).add_field(
name='Server Ping',
value=f'**﹂**``{round(status.latency, 2)} ms``',
# value=f'**⌊** **🏓 Pong!** with **``{round(status.latency, 2)}``** ms'
).add_field(
name='Players online',
value=f'**﹂** ``{status.players.online} players``',
)
)
except asyncio.TimeoutError or OSError:
# ToDo: Check os Error on booting
# ToDo: add is starting and create embed
await ctx.respond(
embed=discord.Embed(
title=f'Server offline',
description=f'(**{self.bot.config.server_address}** on port {self.bot.config.server_port})',
colour=self.bot.color,
timestamp=datetime.now()
)
)

9
bot/libs/__init__.py Normal file
View File

@@ -0,0 +1,9 @@
"""
Project is under GNU GENERAL PUBLIC LICENSE 3.0
2022, created by Specoolazius
"""
from .config import Configs
from .presence import Presence
from .client import Client

182
bot/libs/client.py Normal file
View File

@@ -0,0 +1,182 @@
"""
Project is under GNU GENERAL PUBLIC LICENSE 3.0
2022, created by Specoolazius
"""
from abc import ABC
import asyncio
from typing import Any
import re
import os
import random
import time
import logging
import discord
from mcstatus import JavaServer
from libs import Configs, Presence
SHELL_SCRIPT_PATH = 'scripts'
class Client(discord.Bot, ABC):
"""< discord.Bot >
The bot class
"""
def __init__(self):
self.__config = Configs()
self.logger = self.__setup_logger(self.__config.log_level)
super(Client, self).__init__(
# default presence
activity=discord.Game('Beep Boop! Loading...'),
status=discord.Status.idle,
# debug
debug_guilds=self.config.debug_guilds,
)
self.is_server_starting = False
self.last_start = time.time()
self.mc_server = JavaServer(
self.__config.server_address,
self.__config.server_port
)
self.presence_manager = Presence(self)
# extensions
from extensions import Admin, StartStop, Status
for module in [Admin, StartStop, Status]:
self.add_cog(module(self))
def run(self, *args: Any, **kwargs: Any) -> None:
"""< function >
Starts the bot and automatically gets the configured token.
"""
super(Client, self).run(self.__config.auth_token)
@property
def config(self) -> Configs:
"""< property >
The default config should not be changeable even on runtime.
This ensures its read-only.
:return: Butter default config
"""
return self.__config
@property
def color(self) -> int:
"""< property >
Depending on the setting set in ButterConfig either
the bot's default color gets returned or a random
always bright color.
:return: hex-Color
"""
colors: list[int] = [0, 255, random.randint(0, 255)]
random.shuffle(colors)
return int('0x%02x%02x%02x' % tuple(colors), 16)
def __setup_logger(self, level: int = logging.INFO) -> logging.Logger:
"""< function >
Basic logging abilities
"""
path_list = re.split('/| \\\\', self.__config.log_path)
for i, folder in enumerate(path_list):
# filtering empty strings (e.g. for using source folder)
if not folder:
continue
try:
# creates folders
os.mkdir(os.path.join(*path_list[:i + 1]))
except FileExistsError:
continue
logger = logging.getLogger('discord')
logger.setLevel(level)
formatter = logging.Formatter(fmt='[%(asctime)s] - %(levelname)s: %(name)s: %(message)s')
file_handler = logging.FileHandler(filename=f'{os.path.join(*path_list, "") or ""}discord.log',
encoding='utf-8', mode='w')
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
console_handler = logging.StreamHandler()
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)
return logger
async def on_ready(self) -> None:
"""< coroutine >
Logs when the bot is online.
"""
self.logger.info('Bot successfully started')
async def execute_shell(self, file_name: str, retry=True) -> int:
"""< coroutine >
Runs a bash script in executer and returns the process code.
Logs errors if process returncode isn't 0.
:param file_name: file name
:param retry: recursion stopper in case granting permissions fails.
:return: process returncode
"""
async def __grant_permission() -> int:
self.logger.info(f'Granting permissions to {file_name}...')
process_chmod = await asyncio.create_subprocess_shell(
cmd=f'chmod +x {os.path.join(os.getcwd(), SHELL_SCRIPT_PATH, file_name)}'
)
await process_chmod.communicate()
return process_chmod.returncode
if not retry:
await __grant_permission()
try:
process = await asyncio.create_subprocess_exec(
program=os.path.join(os.getcwd(), SHELL_SCRIPT_PATH, file_name),
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
stdout, stderr = await process.communicate()
self.logger.info(f'Executed script {file_name} with exit code {process.returncode}')
if process.returncode == 0:
self.logger.info(f'stdout:\n{stdout.decode()}')
else:
self.logger.error(f'stderr:\n{stderr.decode()}')
return process.returncode
except PermissionError:
# retrying once
self.logger.warning(f'Missing permissions for {file_name}')
return await self.execute_shell(file_name, retry=False)

86
bot/libs/config.py Normal file
View File

@@ -0,0 +1,86 @@
"""
Project is under GNU GENERAL PUBLIC LICENSE 3.0
2022, created by Specoolazius
"""
from datetime import datetime
from configparser import ConfigParser
import re
import os
import logging
import discord
CONFIG_PATH = 'config'
class Configs(object):
"""< object >
This contains all configs and settings defined in config.ini and settings.ini
"""
def __init__(self):
self.start_time = datetime.now()
path_list = re.split('/| \\\\', CONFIG_PATH)
config = ConfigParser(allow_no_value=True)
config.read(f'{os.path.join(*path_list, "") or ""}config.ini')
self.__login_token = config.get('discord', 'token')
self.server_address = config.get('mcserver', 'server_address')
self.server_port = config.getint('mcserver', 'server_port', fallback=25565)
self.admin_ids = config.get('perms', 'admin_ids')
if self.admin_ids:
self.admin_ids = [int(admin_id) for admin_id in self.admin_ids.split(' ')]
self.role_ids = config.get('perms', 'role_ids')
if self.admin_ids:
self.role_ids = [int(admin_id) for admin_id in self.admin_ids.split(' ')]
self.executable_commands = {
'start': config.getboolean('perms', 'start', fallback=True),
'stop': config.getboolean('perms', 'stop', fallback=False),
'status': config.getboolean('perms', 'status', fallback=True),
'dev_commands': config.getboolean('perms', 'false', fallback=False),
}
config.read(f'{os.path.join(*path_list, "") or ""}settings.ini')
self.log_path = config.get('logging', 'path', fallback=os.path.join('logs', ''))
self.log_level = config.getint('logging', 'level', fallback=logging.INFO)
self.retry_in_seconds = config.getint('presence', 'retry_in_seconds', fallback=15)
self.server_start_timout = config.getint('presence', 'server_start_timout', fallback=300)
self.debug_guilds = config.get('debug', 'debug_guilds', fallback=None)
if self.debug_guilds:
self.debug_guilds = [int(guild_id) for guild_id in self.debug_guilds.split(' ')]
self.service_name = config.get('systemd', 'service_name', fallback='mc-status-bot')
self.intents = discord.Intents.none()
self.mc_flags = discord.MemberCacheFlags.from_intents(self.intents)
@property
def auth_token(self) -> str:
"""< property >
This ensures the authentication token can only be used once.
After that it gets deleted for protection reasons
:return: OAuth Token
"""
try:
token = self.__login_token
del self.__login_token
except AttributeError:
raise Exception('OAuth token has already been used once')
else:
return token

87
bot/libs/presence.py Normal file
View File

@@ -0,0 +1,87 @@
"""
Project is under GNU GENERAL PUBLIC LICENSE 3.0
2022, created by Specoolazius
"""
import asyncio
import time
import discord
from discord.ext import tasks
class Presence(object):
"""<object>
A class for simply managing the bot's presence.
"""
def __init__(self, bot):
from libs import Client
bot: Client
self.bot = bot
self.mc_server = self.bot.mc_server
self.retry_in_seconds = self.bot.config.retry_in_seconds
self.__server_online_presence.start()
@tasks.loop()
async def __server_online_presence(self):
_time = time.time()
try:
self.bot.logger.debug('Getting server information')
status = await self.mc_server.async_status()
self.bot.is_server_starting = False
await self.bot.change_presence(
activity=discord.Game(
name=f'with {status.players.online} player{"s" if status.players.online != 1 else ""}',
),
status=discord.Status.online
)
await asyncio.sleep(40)
await self.bot.change_presence(
activity=discord.Activity(
type=discord.ActivityType.watching,
name=self.bot.config.server_address,
)
)
await asyncio.sleep(20)
except asyncio.TimeoutError:
if self.bot.is_server_starting and self.bot.last_start + self.bot.config.server_start_timout < time.time():
self.bot.is_server_starting = False
self.bot.logger.debug('Resetting is_starting to False')
# ToDo: better presence
await self.bot.change_presence(
activity=discord.Activity(
type=discord.ActivityType.watching,
name=f'{self.bot.config.server_address} starting' if self.bot.is_server_starting else
f'offline: [self.server.address]',
),
status=discord.Status.idle
)
# abs -> simple fix if for any reason the sleep_time is negative
sleep_time = abs(_time - time.time() + self.retry_in_seconds)
self.bot.logger.debug(f'Server offline, retrying in {round(sleep_time, 4)} seconds')
await asyncio.sleep(sleep_time)
@__server_online_presence.before_loop
async def __before_status(self) -> None:
"""< coroutine >
Waits until the bot has fully started in case the server
is already online
"""
await self.bot.wait_until_ready()
self.bot.logger.info('presence loaded')

View File

@@ -1,14 +1,18 @@
"""
Project is under GNU GENERAL PUBLIC LICENSE 3.0
2022, created by Specoolazius
"""
import sys import sys
from aiohttp import ClientConnectorError from aiohttp import ClientConnectorError
from libs.bot import ServerBot from libs import Client
if __name__ == '__main__': if __name__ == '__main__':
bot = ServerBot()
try: try:
bot.run() Client().run()
except ClientConnectorError as e: except ClientConnectorError as e:
sys.exit(-59) sys.exit(-59)

8
bot/run.sh Normal file
View File

@@ -0,0 +1,8 @@
#!/bin/bash
# Project is under GNU GENERAL PUBLIC LICENSE 3.0
#
# 2022, created by Specoolazius
cd /root/mcserver-discordbot/bot || exit;
python3 -O run.py

View File

@@ -1 +1,11 @@
#!/bin/bash #!/bin/bash
# Project is under GNU GENERAL PUBLIC LICENSE 3.0
#
# 2022, created by Specoolazius
# Updates bot from https://github.com/Specoolazius/mcserver-discordbot
git stash
git pull --rebase origin

12
mc-status-bot.service Normal file
View File

@@ -0,0 +1,12 @@
# paste this file to /usr/lib/systemd/system/mc-status-bot.service
[Unit]
Description=Minecraft Status Bot
Requires=network.target
[Service]
Type=idle
ExecStart=chmod +x /root/mcserver-discordbot/bot/run.sh && /root/mcserver-discordbot/bot/run.sh
[Install]
WantedBy=default.target