Skip to content

Commit

Permalink
Thread tracking + Command Joining + Server Schema updates
Browse files Browse the repository at this point in the history
Closes #61 #36 #65

Partially patches #64 but needs more work
  • Loading branch information
SocksTheWolf committed Dec 7, 2024
1 parent 4fb1885 commit 2d1eb3b
Show file tree
Hide file tree
Showing 9 changed files with 165 additions and 31 deletions.
38 changes: 20 additions & 18 deletions BotCommands.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ def GetInstance(self):

def IsActivated(self, InteractionId:int) -> bool:
return (self.GetInstance().Database.IsActivatedInServer(InteractionId))

def CanReport(self, InteractionId:int) -> bool:
return (self.GetInstance().Database.CanServerReport(InteractionId))

@app_commands.command(name="check", description="Checks to see if a discord id is banned")
@app_commands.checks.has_permissions(ban_members=True)
Expand All @@ -26,7 +29,7 @@ async def ScamCheck_Global(self, interaction:Interaction, target:app_commands.Tr
else:
await interaction.response.send_message("Your server must be activated in order to run scam check!")

@app_commands.command(name="report", description="Report an User ID")
@app_commands.command(name="report", description="Report an User")
@app_commands.checks.has_permissions(ban_members=True)
@app_commands.checks.cooldown(1, 5.0)
async def ReportScam_Global(self, interaction:Interaction, target:app_commands.Transform[int, TargetIdTransformer]):
Expand All @@ -39,32 +42,31 @@ async def ReportScam_Global(self, interaction:Interaction, target:app_commands.T
await interaction.response.send_message("You must activate your server to report users", ephemeral=True, delete_after=10.0)
return

# Check if the server is barred from reporting
if (not self.CanReport(interaction.guild_id)):
await interaction.response.send_message("Due to potential abuse, this command is limited in this server, please contact support.", ephemeral=True, delete_after=10.0)
return

# If it cannot be transformed, print an error
if (target == -1):
await interaction.response.send_message("Could not look up the given user, supported look up methods are via @ mentions and user ids", ephemeral=True)
return

UserToSend:Member|User|None = await self.GetInstance().LookupUser(target, ServerToInspect=interaction.guild)
# If the user is no longer in said server, then do a global lookup
if (UserToSend is None):
UserToSend = await self.GetInstance().LookupUser(target)

# If the user is still invalid, then ask for a manual report.
if (UserToSend is None):
await interaction.response.send_message("Unable to look up the given user for a report, you'll have to make a report manually.", ephemeral=True)
return

await interaction.response.send_modal(SubmitScamReport(UserToSend))

@app_commands.command(name="reportuser", description="Report a user by a mention selector")
@app_commands.checks.has_permissions(ban_members=True)
@app_commands.checks.cooldown(1, 5.0)
async def ReportScamUser_Global(self, interaction:Interaction, user:Member):
if (interaction.guild_id == Config()["ControlServer"]):
await interaction.response.send_message("This command cannot be used in the control server", ephemeral=True, delete_after=5.0)
return

# Block any usages of the commands if the server is not activated.
if (not self.IsActivated(interaction.guild_id)):
await interaction.response.send_message("You must activate your server to report users", ephemeral=True, delete_after=10.0)
return
# Check if the user is already banned
if (interaction.client.Database.DoesBanExist(UserToSend.id)):
await interaction.response.send_message(f"The targeted user is already banned by ScamGuard.", ephemeral=True, delete_after=20.0)

await interaction.response.send_modal(SubmitScamReport(user))
await interaction.response.send_modal(SubmitScamReport(UserToSend))

@app_commands.command(name="setup", description="Set up ScamGuard")
@app_commands.checks.has_permissions(ban_members=True)
Expand All @@ -78,7 +80,7 @@ async def SetupScamGuard_Global(self, interaction:Interaction):
if (not self.IsActivated(interaction.guild_id)):
await self.GetInstance().ServerSetupHelper.OpenServerSetupModel(interaction)
else:
await interaction.response.send_message("This server is already activated with ScamGuard!", ephemeral=True, delete_after=15.0)
await interaction.response.send_message("This server is already activated with ScamGuard! Use `/scamguard config` to change settings", ephemeral=True, delete_after=15.0)


@app_commands.command(name="config", description="Set ScamGuard Settings")
Expand Down
61 changes: 59 additions & 2 deletions BotDatabase.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,26 @@ def RemoveServerEntry(self, ServerId:int, BotId:int):

self.Database.delete(server)
self.Database.commit()

def ToggleServerBan(self, ServerId:int, NewStatus:bool):
stmt = select(Server).where(Server.discord_server_id==ServerId)
server = self.Database.scalars(stmt).first()
if (server is None):
return

server.should_ban_in = NewStatus
self.Database.add(server)
self.Database.commit()

def ToggleServerReport(self, ServerId:int, NewStatus:bool):
stmt = select(Server).where(Server.discord_server_id==ServerId)
server = self.Database.scalars(stmt).first()
if (server is None):
return

server.can_report = NewStatus
self.Database.add(server)
self.Database.commit()

def SetBotActivationForOwner(self, Servers:list[int], IsActive:bool, BotId:int, OwnerId:int=-1, ActivatorId:int=-1):
NumActivationChanges = 0
Expand Down Expand Up @@ -254,6 +274,18 @@ def IsActivatedInServer(self, ServerId:int) -> bool:
return True

return False

def CanServerReport(self, ServerId:int) -> bool:
if (not self.IsInServer(ServerId)):
return False

stmt = select(Server).where(Server.discord_server_id==ServerId)
server = self.Database.scalars(stmt).first()

if (server.can_report):
return True

return False

def DoesBanExist(self, TargetId:int) -> bool:
stmt = select(Ban).where(Ban.discord_user_id==TargetId)
Expand All @@ -275,7 +307,7 @@ def GetServerInfo(self, ServerId:int) -> Server:
return self.Database.scalars(stmt).first()

### Adding/Removing Bans ###
def AddBan(self, TargetId:int, BannerName:str, BannerId:int) -> BanLookup:
def AddBan(self, TargetId:int, BannerName:str, BannerId:int, ThreadId:int|None) -> BanLookup:
if (self.DoesBanExist(TargetId)):
return BanLookup.Duplicate

Expand All @@ -284,6 +316,9 @@ def AddBan(self, TargetId:int, BannerName:str, BannerId:int) -> BanLookup:
assigner_discord_user_id = BannerId,
assigner_discord_user_name = BannerName
)

if (ThreadId is not None):
ban.evidence_thread = ThreadId

self.Database.add(ban)
self.Database.commit()
Expand All @@ -302,6 +337,22 @@ def RemoveBan(self, TargetId:int) -> BanLookup:

return BanLookup.Good

### Updating Ban Data ###
def SetEvidenceThread(self, TargetId:int, ThreadId:int):
if (TargetId <= 0 or ThreadId <= 0):
return

if (not self.DoesBanExist(TargetId)):
return

stmt = select(Ban).where(Ban.id==TargetId)
if (stmt is None):
return

banToChange = self.Database.scalars(stmt).first()
banToChange.evidence_thread = ThreadId
self.Database.add(banToChange)

### Getting Server Information ###
def GetAllServersOfOwner(self, OwnerId:int) -> list[Server]:
stmt = select(Server).where(Server.owner_discord_user_id==OwnerId)
Expand Down Expand Up @@ -354,20 +405,26 @@ def GetAllBans(self, NumLastActions:int=0) -> list[Ban]:

return list(self.Database.scalars(stmt).all())

def GetAllServers(self, ActivationState:bool=False, OfInstance:int=-1) -> list[Server]:
def GetAllServers(self, ActivationState:bool=False, OfInstance:int=-1, FilterBanability:bool=False) -> list[Server]:
stmt = select(Server)

if (ActivationState):
stmt = stmt.where(Server.activation_state==ActivationState)

if (OfInstance > -1):
stmt = stmt.where(Server.bot_instance_id==OfInstance)

if (FilterBanability):
stmt = stmt.where(Server.should_ban_in==1)

return list(self.Database.scalars(stmt).all())

def GetAllActivatedServers(self, OfInstance:int=-1) -> list[Server]:
return self.GetAllServers(True, OfInstance)

def GetAllActivatedServersWithBans(self, OfInstance:int=-1) -> list[Server]:
return self.GetAllServers(True, OfInstance, True)

def GetAllDeactivatedServers(self) -> list[Server]:
stmt = select(Server).where(Server.activation_state==False)

Expand Down
5 changes: 4 additions & 1 deletion BotDatabaseSchema.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from sqlalchemy import Column, Integer, DateTime, String
from sqlalchemy.sql import func
from sqlalchemy.sql import func, null
from sqlalchemy.orm import declarative_base

Base = declarative_base()
Expand All @@ -21,6 +21,7 @@ class Ban(Base):
assigner_discord_user_name = Column(String(32), nullable=False)
created_at = Column(DateTime(), server_default=func.now())
updated_at = Column(DateTime(), server_default=func.now(), onupdate=func.now())
evidence_thread = Column(Integer, nullable=True, server_default=null())

class Server(Base):
__tablename__ = "servers"
Expand All @@ -36,3 +37,5 @@ class Server(Base):
message_channel = Column(Integer, server_default="0")
has_webhooks = Column(Integer, server_default="0")
kick_sus_users = Column(Integer, server_default="0")
can_report = Column(Integer, server_default="1")
should_ban_in = Column(Integer, server_default="1")
4 changes: 3 additions & 1 deletion BotEnums.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class BanResult(CompareEnum):
NotBanned=auto()
InvalidUser=auto()
LostPermissions=auto()
BansExceeded=auto()
ServerOwner=auto()
Error=auto()

Expand All @@ -33,4 +34,5 @@ class RelayMessageType(CompareEnum):
# TODO: In future to remove the number of writers
AddedToServer=auto()
RemovedFromServer=auto()
ServerOwnerChanged=auto()
ServerOwnerChanged=auto()
# TODO: Add enqueue messages to fix issue #64
22 changes: 18 additions & 4 deletions BotMain.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,7 @@ async def PostScamReport(self, ReportData):
### Webhook Management ###
async def InstallWebhook(self, ServerId:int):
ChannelID:int = self.Database.GetChannelIdForServer(ServerId)
MessageChannel:discord.TextChannel = self.get_channel(ChannelID)
MessageChannel:discord.TextChannel = self.get_channel(ChannelID)

# Check to see if a webhook is already installed.
if (MessageChannel is not None):
Expand Down Expand Up @@ -467,6 +467,9 @@ async def DeleteWebhook(self, ServerId:int):
def GetServerInfoStr(self, Server:discord.Guild) -> str:
return f"{Server.name}[{Server.id}]"

def GetControlServerGuild(self) -> discord.Guild:
return self.get_guild(ConfigData["ControlServer"])

def PostPongMessage(self):
Logger.Log(LogLevel.Notice, "I have been pinged!")

Expand Down Expand Up @@ -515,6 +518,9 @@ async def CreateBanEmbed(self, TargetId:int) -> discord.Embed:
if (HasUserData):
UserData.add_field(name="Name", value=User.display_name)
UserData.add_field(name="Handle", value=User.mention)
# If currently banned and has an evidence thread, display it.
if (UserBanned and BanData.evidence_thread is not None):
UserData.add_field(name="Evidence Thread (TAG Server)", value=f"<#{BanData.evidence_thread}>")
# This will always be an approximation, plus they may be in servers the bot is not in.
if (ConfigData["ScamCheckShowsSharedServers"]):
UserData.add_field(name="Shared Servers", value=f"~{len(User.mutual_guilds)}")
Expand Down Expand Up @@ -572,6 +578,10 @@ async def ReprocessBans(self, ServerId:int, LastActions:int=0) -> BanResult:
Logger.Log(LogLevel.Error, f"Unable to process ban on user {UserId} for server {ServerInfoStr}")
BanReturn = BanResult.LostPermissions
break
elif (BanResponseFlag == BanResult.BansExceeded):
Logger.Log(LogLevel.Error, f"Unable to process ban on user {UserId} for server {ServerInfoStr} due to exceed")
BanReturn = BanResult.BansExceeded
break
else:
NumBans += 1
Logger.Log(LogLevel.Notice, f"Processed {NumBans}/{TotalBans} bans for {ServerInfoStr}!")
Expand Down Expand Up @@ -608,7 +618,7 @@ async def ProcessActionOnUser(self, TargetId:int, AuthorizerName:str, IsBan:bool
ScamStr = "non-scammer"

BanReason=f"Confirmed {ScamStr} by {AuthorizerName}"
AllServers = self.Database.GetAllActivatedServers(self.BotID)
AllServers = self.Database.GetAllActivatedServersWithBans(self.BotID)
NumServers:int = len(AllServers)

# Instead of going through all servers it's added to, choose all servers that are activated.
Expand Down Expand Up @@ -636,7 +646,7 @@ async def ProcessActionOnUser(self, TargetId:int, AuthorizerName:str, IsBan:bool
elif (ResultFlag == BanResult.ServerOwner):
Logger.Log(LogLevel.Error, f"Attempted to ban a server owner! {self.GetServerInfoStr(DiscordServer)} with user to work {UserToWorkOn.id} == {DiscordServer.owner_id}")
continue
elif (ResultFlag == BanResult.LostPermissions or ResultFlag == BanResult.Error):
elif (ResultFlag == BanResult.LostPermissions or ResultFlag == BanResult.Error or ResultFlag == BanResult.BansExceeded):
self.AddAsyncTask(self.PostBanFailureInformation(DiscordServer, TargetId, ResultFlag, IsBan))
else:
# TODO: Potentially remove the server from the list?
Expand Down Expand Up @@ -678,6 +688,10 @@ async def PerformActionOnServer(self, Server:discord.Guild, User:discord.Member,
Logger.Log(LogLevel.Error, f"We do not have ban/unban permissions in this server {ServerInfo} owned by {ServerOwnerId}! Err: {str(forbiddenEx)}")
return (False, BanResult.LostPermissions)
except discord.HTTPException as ex:
if (ex.code == 30035):
Logger.Log(LogLevel.Warn, f"Hit the bans exceeded error while trying to perform actions on server {ServerInfo}")
return (False, BanResult.BansExceeded)

Logger.Log(LogLevel.Warn, f"We encountered an error {(str(ex))} while trying to perform for server {ServerInfo} owned by {ServerOwnerId}!")
return (False, BanResult.Error)

Expand All @@ -701,7 +715,7 @@ async def PostBanFailureInformation(self, Server:discord.Guild, UserId:int, Reas
if (Reason == BanResult.LostPermissions):
ErrorMsg = "ScamGuard does not have significant permissions to ban this user"
ResolutionMsg = "This usually happens if the user in question has grabbed roles that are higher than the bot's.\n\nYou can usually fix this by changing the order as seen in [this video](https://youtu.be/XYaQi3hM9ug), or giving ScamGuard a moderation role."
elif (Reason == BanResult.Error):
elif (Reason == BanResult.Error or Reason == BanResult.BansExceeded):
ErrorMsg = "ScamGuard encountered an unknown error"
ResolutionMsg = "This can happen when the Discord API has a hiccup, a ban will retry again soon."
else:
Expand Down
10 changes: 9 additions & 1 deletion BotSetup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from BotDatabaseSchema import Base, Migration, Ban, Server

class DatabaseMigrator:
DATABASE_VERSION=4
DATABASE_VERSION=5
VersionMap={}
DatabaseCon=None

Expand Down Expand Up @@ -126,6 +126,14 @@ def upgrade_version3to4(self) -> bool:
session.execute(text("ALTER TABLE servers ADD kick_sus_users INTEGER default 0"))
session.commit()
return True

def upgrade_version4to5(self) -> bool:
session = Session(self.DatabaseCon)
session.execute(text("ALTER TABLE bans ADD evidence_thread INTEGER default NULL"))
session.execute(text("ALTER TABLE servers ADD can_report INTEGER default 1"))
session.execute(text("ALTER TABLE servers ADD should_ban_in INTEGER default 1"))
session.commit()
return True

def SetupDatabases():
Logger.Log(LogLevel.Notice, "Loading database for scam bot setup")
Expand Down
4 changes: 2 additions & 2 deletions ConfirmBanView.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class ConfirmBan(SelfDeletingView):
Hook:WebhookMessage = None

def __init__(self, target:int, bot):
super().__init__(ViewTimeout=60.0)
super().__init__(ViewTimeout=90.0)
self.TargetId = target
self.ScamBot = bot

Expand All @@ -30,7 +30,7 @@ async def confirm(self, interaction: Interaction, button: ui.Button):

await interaction.response.defer(thinking=True)
self.HasInteracted = True
Result:BanLookup = await self.ScamBot.HandleBanAction(self.TargetId, Sender, True)
Result:BanLookup = await self.ScamBot.HandleBanAction(self.TargetId, Sender, True, interaction.channel_id)
if (Result is not BanLookup.Banned):
if (Result == BanLookup.Duplicate):
ResponseMsg = f"{self.TargetId} already exists in the ban database"
Expand Down
Loading

0 comments on commit 2d1eb3b

Please sign in to comment.