-
Notifications
You must be signed in to change notification settings - Fork 15
Feat/220 overwatch functions #234
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
portalBlock
wants to merge
36
commits into
main
Choose a base branch
from
feat/220_overwatch_functions
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
36 commits
Select commit
Hold shift + click to select a range
44e8340
Add Overwatch profile rules cog.
portalBlock 4678785
Add the log channel command to set the output channel (oops, missed t…
portalBlock a5ab5a5
Use the correct config access when loading rules.
portalBlock a6e0b00
Correct a breaking typo.
portalBlock 0574223
Correct a missed config access error.
portalBlock 7d3ec2a
Change matchers to rules for more clarity, remove some default config…
portalBlock 586983c
Missed the adding logic while renaming before.
portalBlock 5cce5b4
Add back the default config? I'm not even sure what's going on with t…
portalBlock 1fe7a57
Fill out the default config more as a sample. Restore (and hopefully …
portalBlock 29220d2
Add rule list command.
portalBlock f9ace2a
For a thing
portalBlock c76a81e
Fix a typo...
portalBlock 44a8621
Rename ow_profile to owprofile and create owvoice cog base.
portalBlock 66f5245
Implement Overwatch voice capabilities.
portalBlock b3e08e4
Attempt to fix a perfectly sensible error.
portalBlock 1560083
IDE auto import at it again.
portalBlock 5667859
Oh wait, I forgot to await.
portalBlock 29b04e9
Fix owvoice cog issues.
portalBlock e38d167
Used a relative time not anchored time, oops.
portalBlock 55c6fd4
Remove ow naming.
portalBlock 5ae3dc5
Standardize the command to set log channels.
portalBlock 6886791
Add the base for MessageWatch - config only, no logic.
portalBlock f3416a6
Complete most of the MessageWatch implementation. Entirely untested.
portalBlock 1d2f20a
Make things async.
portalBlock ccaaa1a
Typo
portalBlock 3c3bb23
Change context, update/standardize naming.
portalBlock d0385db
For to await
portalBlock 7a33dd1
Typo
portalBlock fb2cb4e
Correct timezone for comparison
portalBlock eecbfe3
Correct timezone for comparison
portalBlock 8790e4d
Update docs for PR.
portalBlock b06dd81
Meld all watcher cogs into one large watcher cog
portalBlock 7541465
Fix imports
portalBlock 41b4562
Implement PR feedback, reduce other duplicate code. Untested with sub…
portalBlock 7d7f007
Fix arg parser for profile watcher.
portalBlock 0299665
Correct format for profile rule to string conversion.
portalBlock File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
from redbot.core.bot import Red | ||
|
||
from .messagewatch import MessageWatchCog | ||
|
||
|
||
async def setup(bot: Red): | ||
await bot.add_cog(MessageWatchCog(bot)) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
{ | ||
"author": [ | ||
"portalBlock" | ||
], | ||
"short": "Monitor the frequency of messages and embeds.", | ||
"description": "Analyzes message and embed activity in channels to detect suspicious actions.", | ||
"disabled": false, | ||
"name": "messagewatch", | ||
"tags": [ | ||
"utility", | ||
"mod" | ||
], | ||
"install_msg": "Usage: `[p]messagewatch add`", | ||
"min_bot_version": "3.5.1" | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
from datetime import datetime, timedelta, timezone | ||
from typing import Optional, List | ||
|
||
import discord | ||
from redbot.core import Config, checks | ||
from redbot.core.bot import Red | ||
from redbot.core import commands | ||
from redbot.core.utils.mod import is_mod_or_superior | ||
|
||
|
||
class MessageWatchCog(commands.Cog): | ||
"""MessageWatch Cog""" | ||
|
||
def __init__(self, bot: Red): | ||
self.bot = bot | ||
self.config = Config.get_conf(self, identifier=128986274420752384004) | ||
self.embed_speeds = {} | ||
default_guild_config = { | ||
"logchannel": "", # Channel to send alerts to | ||
"recent_fetch_time": 15000, # Time in milliseconds to fetch recent prior embed times used for calculations. | ||
"frequencies": { # Collection of allowable frequencies | ||
"embed": 1 # Allowable frequency for embeds | ||
}, | ||
"exemptions": { | ||
"member_duration": 30, # Minimum member joined duration required to qualify for any exemptions | ||
"text_messages": 1, # Minimum text-only message frequency required to exempt a user | ||
} | ||
} | ||
|
||
self.config.register_guild(**default_guild_config) | ||
|
||
@checks.admin() | ||
@commands.group("messagewatch", aliases=["mw"], pass_context=True) | ||
async def _messagewatch(self, ctx: commands.Context): | ||
pass | ||
|
||
@_messagewatch.command(name="logchannel") | ||
async def _messagewatch_logchannel(self, ctx: commands.Context, channel: Optional[discord.TextChannel]): | ||
"""Set/update the channel to send message activity alerts to.""" | ||
|
||
chanId = ctx.channel.id | ||
if channel: | ||
chanId = channel.id | ||
await self.config.guild(ctx.guild).logchannel.set(chanId) | ||
await ctx.send("✅ Alert channel successfully updated!") | ||
|
||
@_messagewatch.command(name="fetchtime") | ||
async def _messagewatch_fetchtime(self, ctx: commands.Context, time: str): | ||
"""Set/update the recent message fetch time (in milliseconds).""" | ||
try: | ||
val = float(time) | ||
await self.config.guild(ctx.guild).recent_fetch_time.set(val) | ||
await ctx.send("Recent message fetch time successfully updated!") | ||
except ValueError: | ||
await ctx.send("Recent message fetch time FAILED to update. Please specify a `float` value only!") | ||
|
||
@_messagewatch.group("frequencies", aliases=["freq", "freqs"], pass_context=True) | ||
async def _messagewatch_frequencies(self, ctx: commands.Context): | ||
pass | ||
|
||
@_messagewatch_frequencies.command(name="embed") | ||
async def _messagewatch_frequencies_embed(self, ctx: commands.Context, frequency: str): | ||
"""Set/update the allowable embed frequency.""" | ||
try: | ||
val = float(frequency) | ||
await self.config.guild(ctx.guild).frequencies.embed.set(val) | ||
await ctx.send("Allowable embed frequency successfully updated!") | ||
except ValueError: | ||
await ctx.send("Allowable embed frequency FAILED to update. Please specify a `float` value only!") | ||
|
||
@_messagewatch.group("exemptions", aliases=["exempt", "exempts"], pass_context=True) | ||
async def _messagewatch_exemptions(self, ctx: commands.Context): | ||
pass | ||
|
||
@_messagewatch_exemptions.command(name="memberduration", aliases=["md"]) | ||
async def _messagewatch_exemptions_memberduration(self, ctx: commands.Context, time: str): | ||
"""Set/update the minimum member duration, in hours, to qualify for exemptions.""" | ||
try: | ||
val = int(time) | ||
await self.config.guild(ctx.guild).exemptions.member_duration.set(val) | ||
await ctx.send("Minimum member duration successfully updated!") | ||
except ValueError: | ||
await ctx.send("Minimum member duration FAILED to update. Please specify a `integer` value only!") | ||
|
||
@_messagewatch_exemptions.command(name="textmessages", aliases=["text"]) | ||
async def _messagewatch_expemptions_textmessages(self, ctx: commands.Context, frequency: str): | ||
"""Set/update the minimum frequency of text-only messages to be exempt.""" | ||
try: | ||
val = float(frequency) | ||
await self.config.guild(ctx.guild).exemptions.text_messages.set(val) | ||
await ctx.send("Text-only message frequency exemption successfully updated!") | ||
except ValueError: | ||
await ctx.send("Text-only message frequency exemption FAILED to update. Please specify a `float` value " | ||
"only!") | ||
|
||
@commands.Cog.listener() | ||
async def on_message(self, message: discord.Message): | ||
if await is_mod_or_superior(self.bot, message): # Automatically exempt mods/admin | ||
return | ||
for i in range(len(message.attachments)): | ||
await self.add_embed_time(message.guild, message.author, datetime.utcnow()) # TODO: Use message timestamp | ||
for i in range(len(message.embeds)): | ||
await self.add_embed_time(message.guild, message.author, datetime.utcnow()) # TODO: Use message timestamp | ||
await self.analyze_speed(message.guild, message) | ||
|
||
@commands.Cog.listener() | ||
async def on_message_edit(self, before: discord.Message, after: discord.Message): | ||
if await is_mod_or_superior(self.bot, before): # Automatically exempt mods/admins | ||
return | ||
total_increase = len(after.attachments) - len(before.attachments) | ||
total_increase += len(after.embeds) - len(before.attachments) | ||
if total_increase > 0: | ||
for i in range(total_increase): | ||
await self.add_embed_time(after.guild, # Use the ctx guild because edits are inconsistent, TODO: Message time | ||
after.author if after.author is not None else before.author, datetime.utcnow()) | ||
await self.analyze_speed(after.guild, after) | ||
|
||
async def get_embed_times(self, guild: discord.Guild, user: discord.User) -> List[datetime]: | ||
if guild.id not in self.embed_speeds: | ||
self.embed_speeds[guild.id] = {} | ||
if user.id not in self.embed_speeds[guild.id]: | ||
self.embed_speeds[guild.id][user.id] = [] | ||
return self.embed_speeds[guild.id][user.id] | ||
|
||
async def add_embed_time(self, guild: discord.Guild, user: discord.User, time: datetime): | ||
await self.get_embed_times(guild, user) # Call to get the times to build the user's cache if not already exists | ||
self.embed_speeds[guild.id][user.id].append(time) | ||
|
||
async def get_recent_embed_times(self, guild: discord.Guild, user: discord.User) -> List[datetime]: | ||
filter_time = datetime.utcnow() - timedelta(milliseconds=await self.config.guild(guild).recent_fetch_time()) | ||
return [time for time in await self.get_embed_times(guild, user) if time >= filter_time] | ||
|
||
async def analyze_speed(self, guild: discord.Guild, trigger: discord.Message): | ||
"""Analyzes the frequency of embeds & attachments by a user. Should only be called upon message create/edit.""" | ||
|
||
embed_times = await self.get_recent_embed_times(guild, trigger.author) | ||
|
||
# Check if we have enough basic data to calculate the frequency (prevents some config fetches below) | ||
if len(embed_times) < 2: | ||
return | ||
|
||
# This is a bit of a hack but check if the total embeds, regardless of times, could exceed the frequency limit | ||
# This is needed because one message with N > 1 embeds and no prior embed times would always trigger. | ||
allowable_embed_frequency = await self.config.guild(guild).frequencies.embed() | ||
fetch_time = await self.config.guild(guild).recent_fetch_time() | ||
if len(embed_times) < allowable_embed_frequency * fetch_time: | ||
return | ||
|
||
first_time = embed_times[0] | ||
last_time = embed_times[len(embed_times) - 1] | ||
embed_frequency = len(embed_times) / ((last_time - first_time).microseconds / 1000) # convert to milliseconds | ||
if embed_frequency > allowable_embed_frequency: | ||
# Alert triggered, send unless exempt | ||
|
||
# Membership duration exemption | ||
allowable = trigger.author.joined_at + timedelta( | ||
hours=await self.config.guild(guild).exemptions.member_duration()) | ||
if datetime.now(timezone.utc) < allowable: # Todo: this isn't supposed to exempt them, just allow exempts | ||
# Text-only message exemption (aka active participation exemption) | ||
# TODO | ||
return | ||
|
||
# No exemptions at this point, alert! | ||
# Credit: Taken from report Cog | ||
log_id = await self.config.guild(guild).logchannel() | ||
log = None | ||
if log_id: | ||
log = guild.get_channel(log_id) | ||
if not log: | ||
# Failed to get the channel | ||
return | ||
|
||
data = self.make_alert_embed(trigger.author, trigger) | ||
|
||
mod_pings = " ".join( | ||
[i.mention for i in log.members if not i.bot and str(i.status) in ["online", "idle"]]) | ||
if not mod_pings: # If no online/idle mods | ||
mod_pings = " ".join([i.mention for i in log.members if not i.bot]) | ||
|
||
await log.send(content=mod_pings, embed=data) | ||
# End credit | ||
def make_alert_embed(self, member: discord.Member, message: discord.Message) -> discord.Embed: | ||
"""Construct the alert embed to be sent""" | ||
# Copied from the report Cog. | ||
return ( | ||
discord.Embed( | ||
colour=discord.Colour.orange(), | ||
description="High frequency of embeds detected from a user." | ||
) | ||
.set_author(name="Suspicious User Activity", icon_url=member.avatar.url) | ||
.add_field(name="Server", value=member.guild.name) | ||
.add_field(name="User", value=member.mention) | ||
.add_field(name="Message", | ||
value=f"https://discord.com/channels/{message.guild.id}/{message.channel.id}/{message.id}") | ||
.add_field(name="Timestamp", value=f"<t:{int(datetime.now().utcnow().timestamp())}:F>") | ||
) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
from redbot.core.bot import Red | ||
|
||
from .profilewatch import ProfileWatchCog | ||
|
||
|
||
async def setup(bot: Red): | ||
await bot.add_cog(ProfileWatchCog(bot)) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
{ | ||
"author": [ | ||
"portalBlock" | ||
], | ||
"short": "Monitor profile names for patterns.", | ||
"description": "Utilize regex pattern matching to check new users' names and nicknames.", | ||
"disabled": false, | ||
"name": "profilewatch", | ||
"tags": [ | ||
"utility", | ||
"mod" | ||
], | ||
"install_msg": "Usage: `[p]profilewatch add`", | ||
"min_bot_version": "3.5.1" | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.