Skip to content

Commit

Permalink
Merge pull request #75 from Conner-Anderson/dev
Browse files Browse the repository at this point in the history
Merge Dev into main for 0.2.1 update
  • Loading branch information
Conner-Anderson authored Mar 3, 2023
2 parents 9b21f44 + ff0f355 commit ddcffa9
Show file tree
Hide file tree
Showing 19 changed files with 1,090 additions and 700 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ testbed.py
*.zip
chatbotprocessors/custom_question_processors.py
/dist/
/test.py
37 changes: 37 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
## CHANGELOG

All noteable changes to this project will be documented in this file.

### 0.2.1 Minor Release

#### Features

- The `/tag tree` command is now `/tag_tree`, which means its permissions can be managed apart from the rest of the `/tag` command group.
- `/game_plot` is a new command that posts a plot of the game just like `/post_panel` can, but in a simpler command.
- Obvious configuration mistakes are clearly noted to the console.
- The bot only overwrites as many columns in the Google Sheet as is necessary. This means you can put notes in columns to the right of the data.
- The bot now can use locations for timezones, which is much preferred since it will account for such things as Daylight Savings Time.

#### Bug Fixes
- "registration" and "tag_logging" config options now function.
- Handles reconnecting to the Discord servers on an outage with less irrelevant errors.
- The `/member delete` command can now delete players who have left the server.
- The Game Plot feature (accessible through `/post_panel`) now handles revoked tags properly.
- The `/tag tree` command now shows OZs who have no tags yet. This works even if they are not labeled as an OZ by the `/oz` command.
- A bad configuration file now crashes the bot with more helpful error messages.

#### Minor Changes

- Events that raise exceptions are reported in nice loguru formatting.
#### Breaking Changes

<font color="yellow"> No breaking changes, but there are notes: </font>

- Since the game timezone can now be defined by location, a new note explains this in `config.yml` just above the `timezone:` setting.
You may wish to copy the new note into your `config.yml`.
- You should go to *Server Settings > Integrations* on your server and set the permissions you want for `/tag_tree` and `/game_plot`.

All user files and configurations will function from the previous version.
### 0.2.0 Major Release

The first "ready for the public" release of Discord-HvZ.
16 changes: 3 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,9 @@ For *lots* more information, go to the documentation site:

## [Documentation](https://conner-anderson.github.io/discord-hvz-docs/)

## Version 0.2.0 Released!

This is the second official release and includes *major* changes from the previous alpha release.
While this is no longer called an "alpha" release, any version number below 1.0.0 may have breaking changes.
Read the release information for details on how to update.

#### Important Changes:
- Changed the commands to Slash Commands
- Implemented more robust customization
- Rewrote the fan-favorite feature of the tag tree and added an auto-generating graph that tracks player numbers
- Created multiple install methods
- Wrote an extensive documentation site
- Updated and improved many things under the hood
## Version 0.2.1 Released

This is the third official release and includes moderate changes based on real game testing at LeTourneau University. See the changelog in [Releases](https://github.com/Conner-Anderson/discord-hvz/releases).



Expand Down
51 changes: 28 additions & 23 deletions admin_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,15 @@ def setup(bot): # this is called by Pycord to setup the cog
bot.add_cog(AdminCommandsCog(bot)) # add the cog to the bot


class AdminCommandsCog(commands.Cog):
class AdminCommandsCog(commands.Cog, guild_ids=guild_id_list):

def __init__(self, bot: "HVZBot"):
self.bot = bot

member_group = SlashCommandGroup("member", "Commands for dealing with members.", guild_ids=guild_id_list)
tag_group = SlashCommandGroup("tag", "Commands for dealing with tags.", guild_ids=guild_id_list)

@member_group.command(guild_ids=guild_id_list, name='delete')
@member_group.command(name='delete')
async def member_delete(
self,
ctx,
Expand All @@ -70,31 +70,35 @@ async def member_delete(
msg += 'Both member and ID supplied. Ignoring ID.\n'

elif id:
try:
int(id)
except ValueError:
await ctx.respond(f'"{id}" is not a number. Example of a Discord member ID: 293465952895631360')
return
member_id = id
member: discord.Member = bot.get_member(id)
else:
await ctx.respond('No member or ID provided: nothing deleted.')
return
try:
member_row = bot.db.get_member(member)
member_row = bot.db.get_member(member_id)
except ValueError:
await ctx.respond('That member is not in the database, and so there is nothing to delete.')
await ctx.respond('That member is not in the database as a player, and so there is nothing to delete.')
return
bot.db.delete_row('members', 'id', member_id)
bot.db.delete_row('members', 'id', str(member_id))

if member:
await member.remove_roles(bot.roles['human'])
await member.remove_roles(bot.roles['zombie'])
await member.remove_roles(bot.roles['player'])
msg += f'<@{member_id}> deleted from the game. Roles revoked, expunged from the database.'
else:
msg += f'{member_row.id} deleted from the game and expunged from the database.'
msg += f'{member_row.name} deleted from the game and expunged from the database.'

msg += ' Any tags will still exist.'

await ctx.respond(msg)

@member_group.command(guild_ids=guild_id_list, name='edit')
@member_group.command(name='edit')
async def member_edit(
self,
ctx,
Expand Down Expand Up @@ -126,7 +130,7 @@ async def member_edit(
f'The value of {attribute} for <@{member_row.id}> was changed from \"{original_value}\"" to \"{value}\"')
# bot.sheets_interface.export_to_sheet('members')

@member_group.command(guild_ids=guild_id_list, name='list')
@member_group.command(name='list')
async def member_list(self, ctx):
"""
Lists all members. The Google Sheet is probably better.
Expand All @@ -145,7 +149,7 @@ async def member_list(self, ctx):

await utilities.respond_paginated(ctx, message)

@member_group.command(guild_ids=guild_id_list, name='register')
@member_group.command(name='register')
async def member_register(
self,
ctx,
Expand All @@ -171,10 +175,10 @@ async def member_register(
await ctx.respond('ChatBotManager not loaded. Command failed.')
return

await chatbotmanager.start_chatbot('registration', ctx.author, target_member=member)
await chatbotmanager.start_chatbot('registration', ctx.author, target_member=member, override_config=True)
await ctx.respond('Registration chatbot started in a DM', ephemeral=True)

@tag_group.command(guild_ids=guild_id_list, name='create')
@tag_group.command(name='create')
async def tag_create(self, ctx, member: discord.Member):
"""
Starts a tag log chatbot on behalf of another member.
Expand All @@ -191,10 +195,10 @@ async def tag_create(self, ctx, member: discord.Member):
await ctx.respond('ChatBotManager not loaded. Command failed.')
return

await chatbotmanager.start_chatbot('tag_logging', ctx.author, target_member=member)
await chatbotmanager.start_chatbot('tag_logging', ctx.author, target_member=member, override_config=True)
await ctx.respond('Tag logging chatbot started in a DM', ephemeral=True)

@tag_group.command(guild_ids=guild_id_list, name='delete')
@tag_group.command(name='delete')
async def tag_delete(
self,
ctx,
Expand Down Expand Up @@ -229,7 +233,7 @@ async def tag_delete(
msg = f'Tag {tag_id} deleted. ' + msg
await ctx.respond(msg)

@tag_group.command(guild_ids=guild_id_list, name='edit')
@tag_group.command(name='edit')
async def tag_edit(
self,
ctx,
Expand All @@ -253,7 +257,7 @@ async def tag_edit(
await ctx.respond(
f'The value of {attribute} for tag {tag_row.tag_id} was changed from \"{original_value}\"" to \"{value}\"')

@tag_group.command(guild_ids=guild_id_list, name='revoke')
@tag_group.command(name='revoke')
async def tag_revoke(
self,
ctx,
Expand Down Expand Up @@ -291,7 +295,7 @@ async def tag_revoke(
msg = f'Tag {tag_id} revoked. ' + msg
await ctx.respond(msg)

@tag_group.command(guild_ids=guild_id_list, name='restore')
@tag_group.command(name='restore')
async def tag_restore(
self,
ctx,
Expand Down Expand Up @@ -322,7 +326,7 @@ async def tag_restore(
msg = f'Tag {tag_id} restored. ' + msg
await ctx.respond(msg)

@slash_command(guild_ids=guild_id_list, name='config')
@slash_command(name='config')
async def config_command(
self,
ctx,
Expand Down Expand Up @@ -357,7 +361,7 @@ async def config_command(
config[setting] = choice
await ctx.respond(f'Set \"{setting}\" to \"{choice}\"')

@slash_command(guild_ids=guild_id_list)
@slash_command()
async def code(self, ctx):
"""
Gives you your tag code in a private reply. Keep it secret, keep it safe.
Expand All @@ -374,10 +378,10 @@ async def code(self, ctx):
await ctx.author.send('Sorry, something went wrong with that command. Derp.')
log.exception(e)

@tag_group.command(guild_ids=guild_id_list, name='tree')
@slash_command(name='tag_tree')
async def tag_tree(self, ctx: context.ApplicationContext):
"""
Sends a message with a family tree of the zombies in the game.
Responds with a family tree of the zombies in the game.
"""
bot = self.bot
Expand All @@ -390,7 +394,8 @@ async def tag_tree(self, ctx: context.ApplicationContext):
await utilities.respond_paginated(ctx, tree)


@slash_command(name='shutdown', guild_ids=guild_id_list, description='Shuts down the bot.')

@slash_command(name='shutdown', description='Shuts down the bot.')
async def shutdown(
self,
ctx,
Expand Down Expand Up @@ -422,7 +427,7 @@ async def shutdown(
await bot.close()
time.sleep(1)

@slash_command(guild_ids=guild_id_list, name='oz')
@slash_command(name='oz')
async def oz(
self,
ctx,
Expand Down
2 changes: 2 additions & 0 deletions buttons.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@ def __init__(self, bot: "HVZBot"):

@commands.Cog.listener()
async def on_ready(self):
if self.readied:
return # Don't repeat this on_ready event
self.readied = True
button_options = []
view = discord.ui.View(timeout=None) # A view to hold persistent buttons
Expand Down
32 changes: 24 additions & 8 deletions chatbot.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
if TYPE_CHECKING:
from discord_hvz import HVZBot

from config import config, ConfigError
from config import config, ConfigError, ConfigChecker
from buttons import HVZButton

import chatbotprocessors
Expand Down Expand Up @@ -120,12 +120,13 @@ class ScriptData:
starting_processor: callable = None
ending_processor: callable = None
_postable_button: HVZButton = None
config_checker: ConfigChecker = None

def __str__(self) -> str:
return f'[Type: {self.kind}, Table: {self.table} ]'

@classmethod
def build(cls, kind: str, script: Dict, chatbotmanager: ChatBotManager) -> ScriptData:
def build(cls, kind: str, script: Dict, chatbotmanager: ChatBotManager, config_checker: ConfigChecker = None) -> ScriptData:
if script.get('questions') is None:
raise ConfigError
questions = []
Expand Down Expand Up @@ -185,7 +186,7 @@ def build(cls, kind: str, script: Dict, chatbotmanager: ChatBotManager) -> Scrip
postable_bot=chatbotmanager.bot)

try:
return ScriptData(kind=kind, questions=questions, _postable_button=postable_button, **script)
return ScriptData(kind=kind, questions=questions, _postable_button=postable_button, config_checker=config_checker, **script)
except TypeError as e:
e_text = repr(e)
if 'missing' in e_text:
Expand Down Expand Up @@ -375,33 +376,46 @@ async def save(self):
self.bot.db.add_row(self.script.table, response_map_processed)


class ChatBotManager(commands.Cog):
class ChatBotManager(commands.Cog, guild_ids=guild_id_list):
bot: HVZBot
active_chatbots: Dict[int, ChatBot] = {} # Maps member ids to ChatBots
loaded_scripts: Dict[str, ScriptData] = {}

def __init__(self, bot: HVZBot):
def __init__(self, bot: HVZBot, chatbot_config_checkers: Dict = None):
self.bot = bot
startup_data = bot.get_cog_startup_data(self)

file = open('scripts.yml', mode='r')
scripts_data = yaml.load(file)
file.close()

for kind, script in scripts_data.items():
self.loaded_scripts[kind] = (ScriptData.build(kind, script, chatbotmanager=self))

try:
config_checker = startup_data['config_checkers'][kind]
except KeyError:
config_checker = None

self.loaded_scripts[kind] = (ScriptData.build(kind, script, chatbotmanager=self, config_checker=config_checker))

log.debug('ChatBotManager Initialized')

async def start_chatbot(
self,
chatbot_kind: str,
chat_member: discord.Member,
target_member: discord.Member = None
target_member: discord.Member = None,
override_config: bool = False
) -> None:

script = self.loaded_scripts[chatbot_kind]
if not script.config_checker.get_state() and not override_config:
raise ConfigError(f'The chatbot {chatbot_kind} is disabled in the bot\'s configuration. ')

existing = self.active_chatbots.get(chat_member.id)

new_chatbot = ChatBot(
self.loaded_scripts[chatbot_kind],
script,
self.bot,
chat_member,
target_member
Expand All @@ -420,6 +434,8 @@ async def start_chatbot_from_interaction(self, interaction: discord.Interaction)
await self.start_chatbot(interaction.custom_id, interaction.user)
except ValueError as e:
msg = e
except ConfigError:
msg = f'The {interaction.custom_id} chatbot is not enabled on the server right now.'
except discord.Forbidden:
msg = 'Please check your settings for the server and turn on "Allow Direct Messages."'
except Exception as e:
Expand Down
6 changes: 4 additions & 2 deletions chatbotprocessors/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,16 @@ def fetch_functions(module):
try:
import custom_question_processors
except ImportError:
logger.debug('custom_question_processors.py not found')
#logger.debug('custom_question_processors.py not found')
pass
else:
question_processors.update(fetch_functions(custom_question_processors))

try:
import custom_script_processors
except ImportError:
logger.debug('custom_script_processors.py not found')
#logger.debug('custom_script_processors.py not found')
pass
else:
script_processors.update(fetch_functions(custom_script_processors))

Expand Down
Loading

0 comments on commit ddcffa9

Please sign in to comment.