Skip to content

Commit a7d7d05

Browse files
committed
Make bot use App Commands
1 parent 3c0525d commit a7d7d05

26 files changed

+351
-138
lines changed

.github/workflows/main.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ jobs:
1717
run: |
1818
pip install -r requirements.txt -t .
1919
mv bashbot.py __main__.py
20+
echo '{"version": "${{ github.sha }}"}' > build.json
2021
zip -r BashBot.zip .
2122
2223
- name: Create Release

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ venv/
33
__pycache__/
44

55
config.toml
6-
bashbot.log
6+
bashbot.log
7+
state.json

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ docker run -d -v $(pwd)/config.toml:/BashBot/config.toml adikso/bashbot
7070
Now BashBot should start and show later instructions
7171

7272
### Commands
73+
74+
**Commands are now available as app commands**, but you can still use the old way:
75+
7376
(Every command have to start with prefix. By default it's "$". You can change it in settings. More information about commands after typing "$.help")
7477

7578
Command | Alias | Usage | Description

bashbot.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import logging
22

3-
from discord import LoginFailure
3+
from discord import LoginFailure, Intents
44

55
from bashbot.bot import BashBot
66
from bashbot.core.settings import settings
7+
from bashbot.core.state import state
78
from bashbot.core.utils import get_logger
89

910
logger = get_logger('Launcher')
@@ -21,6 +22,7 @@ def launch():
2122
setup_logger()
2223
settings().load()
2324
settings().load_macros()
25+
state().load()
2426

2527
prefix = settings().get('commands.prefixes', ['$'])[0]
2628
token = settings().get('discord.token')
@@ -30,7 +32,9 @@ def launch():
3032
return
3133

3234
try:
33-
BashBot(prefix).run(token)
35+
intents = Intents.default()
36+
intents.message_content = True
37+
BashBot(prefix, intents=intents).run(token)
3438
except LoginFailure as e:
3539
logger.error(e.args[0])
3640

bashbot/bot.py

Lines changed: 51 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
from discord import Message, Reaction, User, Status, Game, DMChannel, Embed
1+
from discord import Message, Status, Game, DMChannel, Embed, Interaction, InteractionType
22
from discord.abc import PrivateChannel
33
from discord.ext.commands import Bot, Context
4+
from discord.ui import View, Button
45
from discord.utils import oauth_url
56

67
from bashbot.command.about import AboutCommand
@@ -21,38 +22,42 @@
2122
from bashbot.core.exceptions import SessionDontExistException, ArgumentFormatException, TerminalNotFoundException, \
2223
MacroNotFoundException
2324
from bashbot.core.settings import settings
24-
from bashbot.terminal.control import TerminalControl
25+
from bashbot.core.state import state
2526
from bashbot.terminal.sessions import sessions
2627
from bashbot.terminal.terminal import TerminalState
27-
from bashbot.core.updater import updater
28+
from bashbot.core.updater import updater, Updater
2829
from bashbot.core.utils import get_logger, parse_template, extract_prefix, is_command, remove_prefix
2930

3031

3132
class BashBot(Bot):
3233
logger = get_logger('BashBot')
3334
cmd_logger = get_logger('Command')
3435

35-
def __init__(self, command_prefix, **options):
36-
super().__init__(command_prefix, **options)
37-
self.add_cog(OpenCommand())
38-
self.add_cog(CloseCommand())
39-
self.add_cog(HereCommand())
40-
self.add_cog(FreezeCommand())
41-
self.add_cog(RenameCommand())
42-
self.add_cog(ControlsCommand())
43-
self.add_cog(AboutCommand())
44-
self.add_cog(RepeatCommand())
45-
self.add_cog(MacroCommand())
46-
self.add_cog(SelectCommand())
47-
self.add_cog(InteractiveCommand())
48-
self.add_cog(SubmitCommand())
49-
self.add_cog(ExecCommand())
50-
self.add_cog(WhitelistCommand())
36+
async def setup_hook(self):
37+
await self.add_cog(OpenCommand())
38+
await self.add_cog(CloseCommand())
39+
await self.add_cog(HereCommand())
40+
await self.add_cog(FreezeCommand())
41+
await self.add_cog(RenameCommand())
42+
await self.add_cog(ControlsCommand())
43+
await self.add_cog(AboutCommand())
44+
await self.add_cog(RepeatCommand())
45+
await self.add_cog(MacroCommand())
46+
await self.add_cog(SelectCommand())
47+
await self.add_cog(InteractiveCommand())
48+
await self.add_cog(SubmitCommand())
49+
await self.add_cog(ExecCommand())
50+
await self.add_cog(WhitelistCommand())
5151

5252
self.remove_command("help")
53-
self.add_cog(HelpCommand())
53+
await self.add_cog(HelpCommand())
5454

5555
async def on_ready(self):
56+
if state()['last_run_version'] != Updater.get_local_commit():
57+
self.logger.info('Synchronizing command tree...')
58+
await self.tree.sync()
59+
self.logger.info('Command tree synchronized')
60+
5661
self.__check_for_updates()
5762

5863
self.logger.info(f'Logged in as {self.user.name} ({self.user.id})')
@@ -71,11 +76,14 @@ def __check_for_updates(self):
7176
if settings().get('other.check_for_updates'):
7277
self.logger.info(f'Checking for updates...')
7378

74-
update_details = updater().check_for_updates(rate_limit=False)
75-
if update_details:
76-
self.logger.info(f'New update available. Try running `git pull`. '
77-
f'Commit "{update_details["message"]}" '
78-
f'({update_details["sha"]})')
79+
releases = updater().check_for_updates()
80+
if releases is None:
81+
self.logger.info(f'Failed to fetch updates information')
82+
elif releases:
83+
self.logger.info(
84+
f'New updates available. Try running `git pull`. \n' +
85+
'\n'.join([f'- {x["name"]} ({x["html_url"]})' for x in releases])
86+
)
7987
else:
8088
self.logger.info(f'BashBot is up to date')
8189

@@ -146,6 +154,24 @@ async def on_message(self, message: Message):
146154
if should_delete_any or (should_delete_interactive and terminal.interactive):
147155
await message.delete()
148156

157+
async def on_interaction(self, interaction: Interaction):
158+
if interaction.type != InteractionType.component:
159+
return
160+
161+
terminal = sessions().by_message(interaction.message)
162+
163+
label = interaction.data['custom_id']
164+
if label.startswith('control_') and not terminal:
165+
await interaction.response.send_message(content='This terminal is unavailable', ephemeral=True)
166+
view = View.from_message(interaction.message)
167+
for component in view.children:
168+
if isinstance(component, Button):
169+
component.disabled = True
170+
171+
await interaction.message.edit(view=view)
172+
terminal.state = TerminalState.BROKEN
173+
terminal.refresh()
174+
149175
async def on_command(self, ctx: Context):
150176
if not isinstance(ctx.message.channel, DMChannel):
151177
guild_name = ctx.message.channel.guild.name
@@ -159,20 +185,6 @@ async def on_command(self, ctx: Context):
159185

160186
self.cmd_logger.info(f"[{guild_name}/#{channel_name}] {author_name} invoked command: {content}")
161187

162-
async def on_reaction_add(self, reaction: Reaction, user: User):
163-
if user.bot:
164-
return
165-
166-
terminal = sessions().by_message(reaction.message)
167-
if reaction.emoji not in terminal.controls:
168-
return
169-
170-
control: TerminalControl = terminal.controls[reaction.emoji]
171-
terminal.send_input(control.text)
172-
173-
async def on_reaction_remove(self, reaction: Reaction, user: User):
174-
await self.on_reaction_add(reaction, user)
175-
176188
async def on_command_error(self, ctx: Context, error):
177189
message = None
178190

bashbot/command/about.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,33 @@
22
from discord.ext import commands
33
from discord.ext.commands import Context
44

5-
from bashbot.constants import REPOSITORY_URL, REPOSITORY_AUTHOR, THUMBNAIL_URL, EMBED_COLOR
5+
from bashbot.constants import REPOSITORY_URL, THUMBNAIL_URL, EMBED_COLOR
66
from bashbot.core.settings import settings
77
from bashbot.core.updater import updater
88

99

1010
class AboutCommand(commands.Cog):
11-
@commands.command(
12-
name='.about',
11+
@commands.hybrid_command(
12+
name='about',
13+
aliases=['.about'],
1314
description='Shows information about project'
1415
)
1516
async def about(self, ctx: Context):
1617
embed = Embed(title='About BashBot', description='BashBot is a Discord bot that allows terminal access via chat.', color=EMBED_COLOR)
17-
embed.add_field(name='Github', value=REPOSITORY_URL, inline=False)
18-
embed.add_field(name='Author', value=REPOSITORY_AUTHOR, inline=False)
18+
embed.add_field(name='Source code', value=REPOSITORY_URL, inline=False)
19+
embed.add_field(name='Author', value='[Adikso](https://github.com/Adikso)', inline=False)
1920
embed.add_field(name='Current version', value=updater().get_local_commit(), inline=False)
2021
embed.set_thumbnail(url=THUMBNAIL_URL)
2122

2223
if settings().get('other.check_for_updates'):
23-
update_details = updater().check_for_updates()
24-
if update_details:
25-
embed.add_field(name='New update available', value=f'"{update_details["message"]}"\n{update_details["sha"]}', inline=False)
24+
releases = updater().check_for_updates()
25+
if releases is None:
26+
embed.add_field(name='Failed to fetch updates information', value='Try again later', inline=False)
27+
elif releases:
28+
embed.add_field(name='Updates available', value='\n'.join([f'- [{x["name"]}]({x["html_url"]})' for x in releases]), inline=False)
2629
else:
2730
embed.add_field(name='No updates available', value='BashBot is up to date', inline=False)
31+
else:
32+
embed.add_field(name='Updates check disabled', value='You can re-enable it via "other.check_for_updates" in the configuration file', inline=False)
2833

2934
await ctx.send(embed=embed)

bashbot/command/close.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from discord import Embed
12
from discord.ext import commands
23

34
from bashbot.command import session_exists
@@ -7,9 +8,9 @@
78

89

910
class CloseCommand(commands.Cog):
10-
@commands.command(
11-
name='.close',
12-
aliases=['.c'],
11+
@commands.hybrid_command(
12+
name='close',
13+
aliases=['.close', '.c'],
1314
description='Closes current terminal session'
1415
)
1516
@session_exists()
@@ -26,4 +27,6 @@ async def close(self, ctx):
2627
await message.delete()
2728

2829
sessions().remove(terminal)
29-
await ctx.send(f"`Closed terminal #{terminal.name}`")
30+
31+
embed = Embed(description=f"Closed terminal #{terminal.name}", color=0xff0000)
32+
await ctx.send(embed=embed)

bashbot/command/controls.py

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,30 @@
1-
from discord import Message
1+
import discord
2+
from discord import Message, Interaction, Embed
23
from discord.ext import commands
4+
from discord.ext.commands import Context
5+
from discord.ui import View
36

47
from bashbot.command import session_exists
8+
from bashbot.terminal.control import TerminalControl
59
from bashbot.terminal.sessions import sessions
610
from bashbot.terminal.terminal import Terminal
711

812

13+
async def controls_callback(interaction: Interaction):
14+
label = interaction.data['custom_id']
15+
if label.startswith('control_'):
16+
label = label[len('control_'):]
17+
terminal = sessions().by_message(interaction.message)
18+
control: TerminalControl = terminal.controls[label]
19+
terminal.send_input(control.text)
20+
21+
await interaction.response.defer()
22+
23+
924
class ControlsCommand(commands.Cog):
10-
@commands.group(
11-
name='.controls',
25+
@commands.hybrid_group(
26+
name='controls',
27+
aliases=['.controls'],
1228
description='Manages terminal controls',
1329
usage='add/remove [emoji] [content..]'
1430
)
@@ -18,18 +34,46 @@ async def controls(self, ctx):
1834

1935
@controls.command()
2036
@session_exists()
21-
async def add(self, ctx, emoji_id, content):
37+
async def add(self, ctx: Context, label, content):
2238
terminal: Terminal = sessions().by_channel(ctx.channel)
2339
message: Message = sessions().find_message(terminal)
2440

25-
terminal.add_control(emoji_id, content)
26-
await message.add_reaction(emoji_id)
41+
view = View.from_message(message)
42+
view.timeout = None
43+
44+
button = discord.ui.Button(style=discord.ButtonStyle.gray, label=label, custom_id=f'control_{label}')
45+
button.callback = controls_callback
46+
view.add_item(button)
47+
48+
new_message = await message.edit(view=view)
49+
sessions().update_message_reference(terminal, new_message)
50+
51+
terminal.add_control(label, content)
52+
53+
if ctx.interaction:
54+
embed = Embed(description=f"Control added", color=0x00ff00)
55+
await ctx.reply(embed=embed, ephemeral=False, delete_after=0)
2756

2857
@controls.command()
2958
@session_exists()
30-
async def remove(self, ctx, emoji_id):
59+
async def remove(self, ctx: Context, label: str = None):
3160
terminal: Terminal = sessions().by_channel(ctx.channel)
3261
message: Message = sessions().find_message(terminal)
3362

34-
terminal.remove_control(emoji_id)
35-
await message.remove_reaction(emoji_id, message.author)
63+
terminal.remove_control(label)
64+
view = View.from_message(message)
65+
66+
for component in view.children:
67+
if component.custom_id == label:
68+
view.remove_item(component)
69+
break
70+
else:
71+
await ctx.reply(content="Couldn't find specified control")
72+
return
73+
74+
new_message = await message.edit(view=view)
75+
sessions().update_message_reference(terminal, new_message)
76+
77+
if ctx.interaction:
78+
embed = Embed(description=f"Control removed", color=0xff0000)
79+
await ctx.reply(embed=embed, ephemeral=False, delete_after=0)

bashbot/command/exec.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88

99

1010
class ExecCommand(commands.Cog):
11-
@commands.command(
12-
name='.exec',
13-
aliases=['.e'],
11+
@commands.hybrid_command(
12+
name='exec',
13+
aliases=['.exec', '.e'],
1414
description='Execute single command',
1515
usage='<command...>'
1616
)
@@ -39,4 +39,4 @@ async def exec(self, ctx, *, command):
3939
universal_newlines=True,
4040
)
4141

42-
await ctx.send(output)
42+
await ctx.send(f'```\n{output}\n```')

bashbot/command/freeze.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
1+
from discord import Embed
12
from discord.ext import commands
23

34
from bashbot.command import session_exists
5+
from bashbot.constants import EMBED_COLOR
46
from bashbot.core.exceptions import SessionDontExistException
57
from bashbot.terminal.sessions import sessions
68
from bashbot.terminal.terminal import TerminalState
79

810

911
class FreezeCommand(commands.Cog):
10-
@commands.command(
11-
name='.freeze',
12-
aliases=['.f'],
12+
@commands.hybrid_command(
13+
name='freeze',
14+
aliases=['.freeze', '.f'],
1315
description='Freezes current terminal session'
1416
)
1517
@session_exists()
@@ -25,4 +27,6 @@ async def freeze(self, ctx):
2527
terminal.state = TerminalState.FROZEN
2628

2729
terminal.refresh()
28-
await ctx.send(f"`Changed terminal #{terminal.name} state to {terminal.state.name}`")
30+
31+
embed = Embed(description=f"Changed terminal #{terminal.name} state to {terminal.state.name}", color=EMBED_COLOR)
32+
await ctx.send(embed=embed)

0 commit comments

Comments
 (0)