Skip to content

Commit

Permalink
Merge pull request #124 from ajstewart/release-v1.1.0
Browse files Browse the repository at this point in the history
* Bump version with dev tag

* Added zombie check for friend code channels (#120)

* Added toggle friend channel secret command (#121)

* Added toggle secret command

- Also improved friend channel embeds.

* Updated README and screenshots

* Changed previous message deletion logic (#123)

* Changed previous message deletion logic

- Opens now delete the previous close message and vice versa.

* Updated README

* Release v1.1.0
  • Loading branch information
ajstewart authored Dec 30, 2022
2 parents e603c66 + 1239a20 commit 09ee38a
Show file tree
Hide file tree
Showing 15 changed files with 256 additions and 54 deletions.
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ When creating the schedule Snorlax will check that it has the correct permission

![CloseAndOpen](/screenshots/CloseAndOpen.png)

**Note**: If there is no activity in the channel since the last opening then the bot will self-tidy the opening and close messages in the channel to avoid clutter.
**Note**: The bot self-tidies the messages, i.e. when an opening occurs it will remove the previous close message and vice versa.

### Roles Not Affected By Schedule

Expand Down Expand Up @@ -311,6 +311,9 @@ This can be done with the commands:

**Manual opening and closing only works on channels with an active schedule!**

**Warning**: Manual opening and closing may leave residual open and close messages if a channel has multiple schedules.
This will be fixed in a future update.

### Schedule Global Settings

The following schedules settings apply to all schedules created on the server:
Expand Down Expand Up @@ -357,6 +360,19 @@ A user will see the following message when they attempt to post a friend code in

![FriendCodeRemoval](/screenshots/friend-code-filter-message.png)

The secret setting of a channel can be changed using the command

```
/friend-code-filter toggle-secret
```

The command will provide a list of the channels and what the secret value is and will become.
Autocomplete can be used to narrow down the options presented.

![friend-code-add](/screenshots/friend-code-channel-secret-toggle-01.png)
![friend-code-add](/screenshots/friend-code-channel-secret-toggle-02.png)
![friend-code-add](/screenshots/friend-code-channel-secret-toggle-03.png)

### Friend Code Filtering & Meowth/PokeNav

Some raid bots create individual channels for raids, where of course friend codes need to be shared.
Expand Down
2 changes: 1 addition & 1 deletion bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from cogs.utils.utils import get_logger, get_prefix


__version__ = '1.0.0'
__version__ = '1.1.0'
DOCS_URL = 'placeholder'


Expand Down
70 changes: 57 additions & 13 deletions cogs/fc_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from .utils import db as snorlax_db
from .utils.embeds import get_friend_channels_embed, get_message_embed
from .utils import log_msgs as snorlax_log
from .utils.autocompletes import friend_code_channel_autocomplete


logger = logging.getLogger()
Expand Down Expand Up @@ -104,8 +105,9 @@ async def listFriendChannels(self, interaction: discord.Interaction) -> None:
Returns:
None
"""
friend_db = await snorlax_db.load_friend_code_channels_db()
if interaction.guild.id not in friend_db['guild'].values:
guild_id = interaction.guild_id
friend_db = await snorlax_db.load_friend_code_channels_db(guild_id=guild_id)
if friend_db.empty:
embed = get_message_embed(
"No channels have been set, the filter is not active.",
msg_type='warning'
Expand All @@ -115,10 +117,7 @@ async def listFriendChannels(self, interaction: discord.Interaction) -> None:
ephemeral=True
)
else:
guild_friend_channels = friend_db.loc[
friend_db['guild'] == interaction.guild.id
]
embed = get_friend_channels_embed(guild_friend_channels)
embed = get_friend_channels_embed(friend_db)

await interaction.response.send_message(embed=embed)

Expand Down Expand Up @@ -154,7 +153,7 @@ async def removeFriendChannel(
embed = get_message_embed(msg, msg_type='warning')
ephemeral = True
else:
ok = await snorlax_db.drop_allowed_friend_code_channel(guild, channel)
ok = await snorlax_db.drop_allowed_friend_code_channel(guild.id, channel.id)
if ok:
msg = (
f"{channel.mention} removed from the friend code"
Expand All @@ -172,6 +171,51 @@ async def removeFriendChannel(

await interaction.response.send_message(embed=embed, ephemeral=ephemeral)

@app_commands.command(
name='toggle-secret',
description="Toggle the secret value of a channel on the friend code whitelist."
)
@app_commands.default_permissions(administrator=True)
@app_commands.check(snorlax_checks.interaction_check_bot)
@app_commands.check(snorlax_checks.check_admin_channel)
@app_commands.checks.has_permissions(administrator=True)
@app_commands.autocomplete(channel=friend_code_channel_autocomplete)
async def friend_code_toggle_secret(
self,
interaction: discord.Interaction,
channel: str
):
try:
channel, secret_val = channel.split("-")
# A bit assuming here but it should be ok.
secret_val = True if secret_val == "True" else False
channel = int(channel)
self.bot.get_channel(channel)
except Exception as e:
msg = "That doesn't seem to be a valid channel. Please select a channel from the options provided."
embed = get_message_embed(msg, msg_type='warning')
await interaction.response.send_message(embed=embed, ephemeral=True)

logger.error(f'Failed toggle friend code channel secret in {interaction.guild.name}: {e}.')
return
else:
ok = await snorlax_db.set_friend_code_channel_secret(
guild_id=interaction.guild_id,
channel_id=channel,
secret=secret_val
)
if ok:
msg = "Friend code channel updated successfully."
embed = get_message_embed(msg, msg_type='success')
friend_db = await snorlax_db.load_friend_code_channels_db(guild_id=interaction.guild_id)
channels_embed = get_friend_channels_embed(friend_db)
embeds = [embed, channels_embed]
else:
msg = "Error when attempting to update the friend code channel."
embeds = [get_message_embed(msg, msg_type='error')]

await interaction.response.send_message(embeds=embeds)

@commands.Cog.listener()
async def on_message(self, message: discord.Message) -> None:
"""
Expand Down Expand Up @@ -204,17 +248,17 @@ async def on_message(self, message: discord.Message) -> None:

if origin_channel_id not in allowed_channels['channel'].values:
msg = (
"{}, that looks like a friend code so"
f"{message.author.mention}, that looks like a friend code so"
" Snorlax ate it!\n\n"
"Friend codes are allowed in:"
).format(message.author.mention)
"Friend codes are allowed in:\n\n"
)

for c in allowed_channels[~allowed_channels['secret']]['channel']:
msg += ' <#{}>'.format(c)
msg += f':small_blue_diamond: <#{c}>\n'

if guild_db.loc[message.guild.id]['meowth_raid_category'] != -1:
msg += (
' or any raid channel generated using'
'\n or any raid channel generated using'
' the Pokenav bot.'
)

Expand Down Expand Up @@ -289,7 +333,7 @@ async def on_guild_channel_delete(self, channel: GuildChannel) -> None:
fc_channels = await snorlax_db.load_friend_code_channels_db()

if channel.id in fc_channels['channel'].tolist():
ok = await snorlax_db.drop_allowed_friend_code_channel(channel.guild, channel)
ok = await snorlax_db.drop_allowed_friend_code_channel(channel.guild.id, channel.id)
if ok:
log_channel = await snorlax_db.get_guild_log_channel(channel.guild.id)
if log_channel != -1:
Expand Down
49 changes: 47 additions & 2 deletions cogs/initial.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def __init__(self, bot: commands.bot) -> None:

self.remove_zombie_schedules.start()
self.remove_zombie_guilds.start()
self.remove_zombie_friend_channels.start()

# EVENT LISTENER FOR WHEN THE BOT HAS SWITCHED FROM OFFLINE TO ONLINE.
@commands.Cog.listener()
Expand Down Expand Up @@ -126,6 +127,38 @@ async def remove_zombie_schedules(self) -> None:

logging.info(f"Zombie schedules check completed: {removed} removed.")

@tasks.loop(seconds=900)
async def remove_zombie_friend_channels(self) -> None:
"""Runs a check of friend code whitelist channels to see if a channel has been deleted that was missed.
If a channel is found to be missing then that friend channel is removed.
"""
logging.info("Performing zombie friend channel check.")

removed = 0
fc_df = await snorlax_db.load_friend_code_channels_db()
guilds_df = await snorlax_db.load_guild_db(active_only=True)

for _, row in fc_df[['guild', 'channel']].iterrows():
# If a guild is not active then don't check.
if row['guild'] not in guilds_df.index.to_numpy():
continue
channel = self.bot.get_channel(row['channel'])
if channel is None:
logging.warning(f"Channel {row['channel']} not found! Removing from friend code whitelist database.")
try:
ok = await snorlax_db.drop_allowed_friend_code_channel(int(row['guild']), int(row['channel']))
except Exception as e:
logging.warning(f"Dropping of friend code channel {row['channel']} failed! Error: {e}.")
else:
if ok:
logging.info(f"Dropping of friend code channel {row['channel']} successful.")
removed += 1
else:
logging.warning(f"Dropping of friend code channel {row['channel']} failed! Error: {e}.")

logging.info(f"Zombie friend code channels check completed: {removed} removed.")

@tasks.loop(seconds=900)
async def remove_zombie_guilds(self) -> None:
"""Runs a check of guilds to see if a guild has left which was missed.
Expand Down Expand Up @@ -159,7 +192,19 @@ async def remove_zombie_guilds(self) -> None:
logging.info(f"Zombie guilds check completed: {removed} deactivated.")

@remove_zombie_schedules.before_loop
async def before_timer(self) -> None:
async def before_timer_schedules(self) -> None:
"""
Method to process before the zombie check loop is started.
The purpose is to make sure the bot is ready before starting.
"""
await self.bot.wait_until_ready()

# Delay by 1 min so zombie guild check can complete.
await asyncio.sleep(60)

@remove_zombie_friend_channels.before_loop
async def before_timer_friend_channels(self) -> None:
"""
Method to process before the zombie check loop is started.
Expand All @@ -171,7 +216,7 @@ async def before_timer(self) -> None:
await asyncio.sleep(60)

@remove_zombie_guilds.before_loop
async def before_timer(self) -> None:
async def before_timer_guilds(self) -> None:
"""
Method to process before the zombie check loop is started.
Expand Down
37 changes: 24 additions & 13 deletions cogs/schedules.py
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,7 @@ async def manualClose(
return

# Grab the schedule row
# TODO: Poor logic when multiple schedules on a single channel
row = schedule_db[schedule_db['channel'] == channel.id].iloc[0]

guild_db = await snorlax_db.load_guild_db()
Expand Down Expand Up @@ -1297,21 +1298,19 @@ async def close_channel(
time_format_fill
)

if not silent:
# Check for the previous messages to see if no one has said anything since the last open.
# If the last two messages are the previous open and close messages then these are removed
# to avoid clutter in the channel.
messages = [message async for message in channel.history(limit=2)]
messages_ids = [message.id for message in messages]

last_close_message = await snorlax_db.get_schedule_last_close_message(rowid)
last_open_message = await snorlax_db.get_schedule_last_open_message(rowid)
last_open_message = await snorlax_db.get_schedule_last_open_message(rowid)

if last_close_message in messages_ids and last_open_message in messages_ids:
logger.info(f"Removing previous open and close messages for schedule: {rowid}.")
for message in messages:
await message.delete()
# Remove the previous close message if present.
if last_open_message is not None:
try:
last_open_message = await channel.fetch_message(last_open_message)
except Exception as e:
logger.warning(f"Last open message not found, skipping deletion (error: {e}).")
else:
logger.info(f"Deleting previous open message in {channel.name} in {channel.guild.name}.")
await last_open_message.delete()

if not silent:
# Send the new one
close_message = await channel.send(embed=close_embed)

Expand Down Expand Up @@ -1397,6 +1396,18 @@ async def open_channel(
time_format_fill
)

last_close_message = await snorlax_db.get_schedule_last_close_message(rowid)

# Remove the previous close message if present.
if last_close_message is not None:
try:
last_close_message = await channel.fetch_message(last_close_message)
except Exception as e:
logger.warning(f"Last close message not found, skipping deletion (error: {e}).")
else:
logger.info(f"Deleting previous close message in {channel.name} in {channel.guild.name}.")
await last_close_message.delete()

if not silent:
open_message = await channel.send(embed=open_embed)
logger.debug(f"Updating last open message for schedule {rowid} to {open_message.id}.")
Expand Down
61 changes: 56 additions & 5 deletions cogs/utils/autocompletes.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""
Contains all the autocomplete functions used in app_commands.
"""
"""Contains all the autocomplete functions used in app_commands."""
import logging
import pandas as pd

from discord import Interaction, app_commands
Expand All @@ -9,11 +8,14 @@
from . import db as snorlax_db


logger = logging.getLogger()


async def timezones_autocomplete(
interaction: Interaction,
current: str
) -> list[app_commands.Choice[str]]:
"""The searchable choices for the timezone entry.
"""Obtain the searchable choices for the timezone entry.
Uses pytz.common_timezones() to populate the list.
Expand All @@ -39,7 +41,7 @@ async def schedule_selection_autocomplete(
interaction: Interaction,
current: str
) -> list[app_commands.Choice[str]]:
"""Fetches schedules to present to the user.
"""Fetch schedules to present to the user.
A string is built from the schedule information so the user can recognise the
schedule they want. This is linked to the database id value.
Expand Down Expand Up @@ -83,3 +85,52 @@ async def schedule_selection_autocomplete(
choices = choices[:25]

return choices


async def friend_code_channel_autocomplete(interaction: Interaction, current: str) -> list[app_commands.Choice[str]]:
"""Fetch the friend code channels to present to the user to toggle secret.
The options are formed by creating strings for the user to recognise the channel
while the values are the channel id.
Args:
interaction: The interaction that is performing the command.
current: The current typed entry by the user.
Returns:
The list of SelectOptions with the friend code channels.
"""
guild = interaction.guild

fc_channels_db = await snorlax_db.load_friend_code_channels_db(guild_id=guild.id)

choices = []

if fc_channels_db.empty:
return choices

secret_human = {
True: "Secret ✅",
False: "Secret ❌"
}

for _, row in fc_channels_db.iterrows():
# Check if the channel still exists
try:
channel = int(row['channel'])
interaction.guild.get_channel(channel)
except Exception as e:
logger.error(f"Channel fetch failed for channel {channel} in guild {interaction.guild.name} (error: {e}).")
continue

secret = row['secret']
label = f"#{row['channel_name']}: {secret_human[secret]} -> {secret_human[not secret]}"
value = f"{channel}-{not secret}"

if current.lower() in label.lower():
choices.append(app_commands.Choice(name=label, value=value))

if len(choices) > 25:
choices = choices[:25]

return choices
Loading

0 comments on commit 09ee38a

Please sign in to comment.