Skip to content

Commit 5633b9d

Browse files
committed
Add restart and shutdown commands
1 parent bc26e3e commit 5633b9d

4 files changed

Lines changed: 75 additions & 4 deletions

File tree

shinobu/__main__.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,12 @@
2121
import time
2222
import copy
2323
import argparse
24+
import traceback
2425
import ujson as json
2526
import orjson
2627
import getpass
2728
import uuid
29+
import asyncio
2830
import discord
2931
from discord.ext import commands
3032
from dotenv import load_dotenv
@@ -54,6 +56,10 @@
5456
launch_secrets_cli: bool = launch_args.secrets
5557
launch_installer_cli: bool = launch_args.installer
5658

59+
# Store restart options
60+
restart_message_id: int | None = None
61+
restart_message_channel_id: int | None = None
62+
5763
# Create TokenStore variable (do not initialize yet)
5864
tokenstore: manager.TokenStore | None = None
5965

@@ -557,7 +563,11 @@ def load_extension(self, package: str, *args, **kwargs):
557563
def start_bot() -> bool:
558564
"""Starts Shinobu!"""
559565

560-
global tokenstore, secrets_authority, raw_encryptor, extension_map, password
566+
global tokenstore, secrets_authority, raw_encryptor, extension_map, password, restart_message_id,\
567+
restart_message_channel_id
568+
569+
# Ensure asyncio event loop is always fresh
570+
asyncio.set_event_loop(asyncio.new_event_loop())
561571

562572
# Regenerate bootscript-level objects
563573
tokenstore = manager.TokenStore(
@@ -582,6 +592,8 @@ def start_bot() -> bool:
582592

583593
# Create Shinobu bot instance
584594
bot: runtime.ShinobuBot = runtime.ShinobuBot(command_prefix="sh!", intents=intents, manifest=manifest_path)
595+
bot.restart_message_id = restart_message_id
596+
bot.restart_message_channel_id = restart_message_channel_id
585597
bot.setup_entitlements_loader(ModuleLoader(bot))
586598
bot.load_builtins()
587599

@@ -593,11 +605,26 @@ def start_bot() -> bool:
593605
bot.run(tokenstore.retrieve("TOKEN"))
594606
except KeyboardInterrupt:
595607
pass
608+
except RuntimeError:
609+
# Assume session was closed (i.e. bot restarted)
610+
pass
596611
except:
612+
traceback.print_exc()
597613
print("Bot has crashed.")
598614
should_restart = bot.should_restart
599615

616+
# Run cleanup
600617
bot.cleanup()
618+
619+
# Set restart state and channel
620+
if bot.requested_restart:
621+
should_restart = True
622+
restart_message_id = bot.restart_message_id
623+
restart_message_channel_id = bot.restart_message_channel_id
624+
625+
# Clear bot memory
626+
del bot
627+
601628
return should_restart
602629

603630
def start_secrets_cli():

shinobu/runtime/modules/admin.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import time
2323
import traceback
2424
import base64
25+
import discord
2526
from contextlib import redirect_stdout
2627
from discord.ext import commands
2728
from shinobu.runtime.models import shinobu_cog
@@ -121,5 +122,22 @@ async def eval(self, ctx: commands.Context, *, body: str):
121122
else:
122123
await ctx.send(f':white_check_mark: Evaluation completed in `{exec_time}` seconds.\n```\n{value}\n```')
123124

125+
@commands.command(name="shutdown", description="Shuts the bot down.", aliases=["poweroff"])
126+
@commands.is_owner()
127+
async def shutdown(self, ctx: commands.Context):
128+
await ctx.reply("shutting down :zzz:")
129+
130+
# Shut down bot
131+
await self.bot.close()
132+
133+
@commands.command(name="restart", description="Restarts the bot.", aliases=["reboot"])
134+
@commands.is_owner()
135+
async def restart(self, ctx: commands.Context):
136+
message: discord.Message = await ctx.reply("restarting :arrows_counterclockwise:")
137+
138+
# Request restart then shut down bot
139+
self.bot.request_restart(message=message)
140+
await self.bot.close()
141+
124142
def setup(bot):
125143
bot.add_cog(Admin(bot))

shinobu/runtime/modules/events.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import traceback
2121
import discord
2222
from discord.ext import commands, bridge
23-
from discord.ext.bridge import BridgeExtContext
2423
from shinobu.runtime.models.colors import Colors
2524
from shinobu.runtime.utils import check_slash
2625

@@ -39,9 +38,18 @@ def __init__(self, bot, **kwargs):
3938

4039
@commands.Cog.listener()
4140
async def on_ready(self):
42-
print("Bot is ready, woohoo!")
41+
print("Shinobu Runtime is ready! :3")
4342
print(f"Logged in as {self.bot.user.name}#{self.bot.user.discriminator} ({self.bot.user.id})")
4443

44+
# Handle restart
45+
if self.bot.restart_message_id:
46+
channel: discord.TextChannel | None = self.bot.get_channel(self.bot.restart_message_channel_id)
47+
if not channel:
48+
return
49+
50+
message: discord.PartialMessage = discord.PartialMessage(channel=channel, id=self.bot.restart_message_id)
51+
await message.edit(content="bot restarted! :white_check_mark:")
52+
4553
@commands.Cog.listener()
4654
async def on_bridge_command_error(self, ctx: bridge.BridgeApplicationContext | bridge.BridgeExtContext, error):
4755
is_slash: bool = check_slash.is_slash(ctx)

shinobu/runtime/runtime.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import tomllib
2020
import ujson as json
21+
import discord
2122
from discord.ext import bridge
2223

2324
class ShinobuErrorManager:
@@ -50,7 +51,12 @@ def __init__(self, *args, **kwargs):
5051
self.__errors: ShinobuErrorManager = ShinobuErrorManager()
5152
self.__cog_entitlements_loader = None
5253
self._cleanups = {}
53-
self._should_restart: bool = False
54+
55+
# Restart state
56+
self._should_restart: bool = False # Restart on crash
57+
self._requested_restart: bool = False # Instance owner requested restart
58+
self.restart_message_id: int | None = None
59+
self.restart_message_channel_id: int | None = None
5460

5561
# Load configs
5662
self._config: dict = {}
@@ -121,6 +127,10 @@ def config(self) -> dict:
121127
def should_restart(self) -> bool:
122128
return self._should_restart
123129

130+
@property
131+
def requested_restart(self) -> bool:
132+
return self._requested_restart
133+
124134
@property
125135
def shared_objects(self) -> ShinobuSharedObjects:
126136
return self.__shared_objects
@@ -132,3 +142,11 @@ def errors(self) -> ShinobuErrorManager:
132142
@property
133143
def cog_entitlements_loader(self):
134144
return self.__cog_entitlements_loader
145+
146+
def request_restart(self, message: discord.Message | None = None):
147+
"""Sets bot to restart. This does not restart the bot on its own."""
148+
self._requested_restart = True
149+
150+
if message:
151+
self.restart_message_id = message.id
152+
self.restart_message_channel_id = message.channel.id

0 commit comments

Comments
 (0)