diff --git a/DefaultConstants.py b/DefaultConstants.py index 5c93276..7287edd 100644 --- a/DefaultConstants.py +++ b/DefaultConstants.py @@ -6,6 +6,7 @@ class Constants: if TEST_SERVER: GUILD_ID =313876691082674178 #Guild ID of the discord server whiteListedRoleIDs = [145802742647095296] # IDs of Roles you wish to be white listed for some commands. You can also add user IDs if you want to add an individual without a role + MOD_ROLE_ID = 1096930045685145710 # Used to ping mods to take action on an approved ban appeal # Channel ID the bot will post notifications to KICK_NOTIFICATION_CHANNEL_ID = 1137599805787480214 CB_NOTIFICATION_CHANNEL_ID = 1137599805787480214 @@ -20,9 +21,12 @@ class Constants: EP_NOTIFICATION_CHANNEL_ID = 1137599805787480214 MV_NOTIFICATION_CHANNEL_ID = 1137599805787480214 CONFESSTION_CHANNEL_ID = 1137599805787480214 + APPEAL_CHANNEL_ID = 1137599805787480214 CONFESSION_COMMAND_ID = 1159423004346957835 CONFESS_REVIEW_COMMAND_ID = 1159423004346957834 + APPEAL_COMMAND_ID = 1159321755270250571 + APPEAL_REVIEW_COMMAND_ID = 1159321755270250570 # Leave an empty string if you don't wish to use a proxy for a checker. # Kick/OF/Fansly use nodriver, which doesn't support authenticated proxies @@ -43,6 +47,7 @@ class Constants: else: GUILD_ID =1058859922219081778 #Guild ID of the discord server whiteListedRoleIDs = [1062179283705020486,145802742647095296,1100148453792813086,245364417783398400] # IDs of Roles you wish to be white listed for some commands. You can also add user IDs if you want to add an individual without a role + MOD_ROLE_ID = 1096930045685145710 # Used to ping mods to take action on an approved ban appeal # Channel ID the bot will post notifications to KICK_NOTIFICATION_CHANNEL_ID = 1268796965743886448 CB_NOTIFICATION_CHANNEL_ID = 1268796965743886448 @@ -57,9 +62,12 @@ class Constants: EP_NOTIFICATION_CHANNEL_ID = 1268796965743886448 MV_NOTIFICATION_CHANNEL_ID = 1268796965743886448 CONFESSTION_CHANNEL_ID = 1158240422997528637 + APPEAL_CHANNEL_ID = 1158240422997528637 CONFESSION_COMMAND_ID = 1159321755270250571 CONFESS_REVIEW_COMMAND_ID = 1159321755270250570 + APPEAL_COMMAND_ID = 1159321755270250571 + APPEAL_REVIEW_COMMAND_ID = 1159321755270250570 # Leave an empty string if you don't wish to use a proxy for a checker. # Kick/OF/Fansly use nodriver, which doesn't support authenticated proxies @@ -107,8 +115,10 @@ class Constants: AVATAR_CHECK_TIMER = 130 # Timer for checking last online time before changing between happy/angry avatars STATUS_CHECK_TIMER = 125 # Timer for checking online status and changing the bot status. Also used for record keeping CONFESSION_CHECK_TIMER = 20 # How often new confessions are checked + APPEAL_CHECK_TIMER = 20 # How often new appeals are checked CONFESSION_ALERT_INTERVALS = [0,0,1800,7200,18000,43200] # Seconds between unreveiwed confession alerts. Starts at index 1. 1st alert 0 seconds, 2nd alert 1800 etc. New confessions reset count + APPEAL_ALERT_INTERVALS = [0,0,1800,7200,18000,43200] # Seconds between unreveiwed appeal alerts. Starts at index 1. 1st alert 0 seconds, 2nd alert 1800 etc. New appeals reset count SMART_ALERT_LOOK_AHEAD = 3 #number of hours smart alert looks ahead to make sure conditions are still met (to make sure alerts aren't made too late into a stream) PERCENTAGE_OF_MAX = 0.85 # Percent of maximum users online before a smart alert goes off diff --git a/GenerateDatabase.py b/GenerateDatabase.py index 5d29c1e..07a73ae 100644 --- a/GenerateDatabase.py +++ b/GenerateDatabase.py @@ -79,7 +79,20 @@ ) ''') - +cur.execute('''CREATE TABLE IF NOT EXISTS appeals + ( + appeal_id INTEGER PRIMARY KEY, + appeal TEXT, + appeal_title TEXT, + appeal_status INTEGER, + appealer_id INTEGER, + appealer_name TEXT, + reviewer_id INTEGER, + reviewer_name TEXT, + date_added INTEGER, + date_reviewed INTEGER + ) + ''') platform_list =[ ("chaturbate",0,0,0), diff --git a/README.MD b/README.MD index 699bd65..a6bd5d1 100644 --- a/README.MD +++ b/README.MD @@ -98,9 +98,14 @@ 4. If the requesting ip is in a state that requires age verification, some of these checkers wont work. ### Update History +- 12/17/2024 + - added two new commands + - /ban-appeal - A command anyone can use to appeal a ban - works similarly to /confess but it's logged + - /appeal-review - a command for white listed people to review the ban appeals. If approved, a message is sent to APPEAL_CHANNEL_ID to tell MOD_ROLE_ID to unban + - new constants were added, so update appconstants.py as needed - 11/22/2024 - - Docker File improvements - - fix for manyvids link + - Docker File improvements + - fix for manyvids link - 10/22/2024 - Added ManyVids Support - Because of this many things have changed in Default/App Constants so they will need to be updated with new vars diff --git a/checkers/Twitch.py b/checkers/Twitch.py index b8d0655..56a506f 100644 --- a/checkers/Twitch.py +++ b/checkers/Twitch.py @@ -36,6 +36,8 @@ def isModelOnline(twitchChannelName: str): logger.warning("connection timed out to Twitch. Bot detection or rate limited?") except requests.exceptions.SSLError: logger.warning("SSL Error when attempting to connect to Twitch") + except TypeError: + logger.warning("twitch user banned or doesn't exist") thumbUrl = GetThumbnail(tempThumbUrl, Constants.twitchThumbnail) return isOnline, title, thumbUrl, icon diff --git a/globals.py b/globals.py index 601ab38..626da7d 100644 --- a/globals.py +++ b/globals.py @@ -25,5 +25,6 @@ } confessionIds = {"alert":0} +appealIds = {"alert":0} browserOpen = False \ No newline at end of file diff --git a/plugins/checks.py b/plugins/checks.py index 54f6c30..4efb54d 100644 --- a/plugins/checks.py +++ b/plugins/checks.py @@ -296,7 +296,7 @@ async def smartAlert(rest: alluka.Injected[hikari.impl.RESTClientImpl]) -> None: async def resetUnreviewedConfessions(rest: alluka.Injected[hikari.impl.RESTClientImpl]) -> None: StaticMethods.resetUnfinishedConfessions() db = Database() - value = db.getAllUnreviewed() + value = db.getAllUnreviewedConfessions() if value: minVal = 99 minAlertsId = 0 @@ -314,4 +314,29 @@ async def resetUnreviewedConfessions(rest: alluka.Injected[hikari.impl.RESTClien globals.confessionIds["alert"] = time.time() for k, v in globals.confessionIds.items(): if v < globals.confessionIds[minAlertsId]: - globals.confessionIds[k] = globals.confessionIds[minAlertsId] \ No newline at end of file + globals.confessionIds[k] = globals.confessionIds[minAlertsId] + +@component.with_schedule +@tanjun.as_interval(Constants.APPEAL_CHECK_TIMER) +async def resetUnreviewedAppeals(rest: alluka.Injected[hikari.impl.RESTClientImpl]) -> None: + StaticMethods.resetUnfinishedAppeals() + db = Database() + value = db.getAllUnreviewedAppeals() + if value: + minVal = 99 + minAlertsId = 0 + for val in value: + if val[0] not in globals.appealIds: + globals.appealIds[val[0]] = 1 + if minVal > globals.appealIds[val[0]]: + minVal = globals.appealIds[val[0]] + minAlertsId = val[0] + alertIntervals = Constants.APPEAL_ALERT_INTERVALS + minVal = len(alertIntervals)-1 if minVal > len(alertIntervals)-1 else minVal + if StaticMethods.timeToSeconds(globals.appealIds["alert"]) >= alertIntervals[minVal]: + globals.appealIds[minAlertsId] += 1 + await rest.create_message(channel=Constants.APPEAL_CHANNEL_ID, content=f"There are {len(value)} appeals in need of review =)\n Use to review them") + globals.appealIds["alert"] = time.time() + for k, v in globals.appealIds.items(): + if v < globals.appealIds[minAlertsId]: + globals.appealIds[k] = globals.appealIds[minAlertsId] \ No newline at end of file diff --git a/plugins/commands.py b/plugins/commands.py index e5059f8..c1a8480 100644 --- a/plugins/commands.py +++ b/plugins/commands.py @@ -22,6 +22,35 @@ component = tanjun.Component() +@component.with_slash_command +@tanjun.as_slash_command("ban-appeal", "Appeal a ban.", always_defer= True, default_to_ephemeral= True) +@CommandLogger +async def confess(ctx: tanjun.abc.SlashContext) -> None: + view = MiruViews.AppealModalView(autodefer=False) + await ctx.respond("Pre-type your ban appeal and then hit the submit button when you are ready to submit it.\n Button will time out after a few mins, so re-type command if it doesn't work", components=view) + message = await ctx.fetch_last_response() + await view.start(message) + await view.wait() + await ctx.interaction.delete_initial_response() + +@component.with_slash_command +@tanjun.checks.with_check(StaticMethods.isPermission) +@tanjun.as_slash_command("appeal-review", "View appeals that need to be reviewd for approval or denial",default_to_ephemeral= True, always_defer= True) +async def appealReview(ctx: tanjun.abc.SlashContext, rest: alluka.Injected[hikari.impl.RESTClientImpl]) -> None: + db = Database() + appealId,appeal, title = db.getUnreviewedAppeal() + if appealId: + view = MiruViews.AppealReView(appealId=appealId, tanCtx=ctx, appeal=appeal, rest=rest, title=title) + content = f"## {appealId}:{title}\n``` {appeal} ```" + await ctx.respond(content=content, components=view) + message = await ctx.fetch_last_response() + await view.start(message) + await view.wait() + message = await ctx.fetch_last_response() + await ctx.interaction.delete_message(message) + else: + await ctx.respond("There are no appealss in need of review") + @component.with_slash_command @tanjun.checks.with_check(StaticMethods.isPermission) @tanjun.with_str_slash_option("channelid", "Text channel ID you wish to send a message to in order to test permissions") diff --git a/utils/Database.py b/utils/Database.py index 3175df1..aa5af83 100644 --- a/utils/Database.py +++ b/utils/Database.py @@ -31,7 +31,27 @@ def connectCursor(self): StaticMethods.rebootServer() return conn, cur - # Confessions Table Methods + # Confessions & Appeals Table Methods + def createAppealsTable(self): + conn,cur = self.connectCursor() + cur.execute('''CREATE TABLE IF NOT EXISTS appeals + ( + appeal_id INTEGER PRIMARY KEY, + appeal TEXT, + appeal_title TEXT, + appeal_status INTEGER, + appealer_id INTEGER, + appealer_name TEXT, + reviewer_id INTEGER, + reviewer_name TEXT, + date_added INTEGER, + date_reviewed INTEGER + ) + ''') + conn.commit() + cur.close() + conn.close() + def createConfessionsTable(self): conn,cur = self.connectCursor() cur.execute('''CREATE TABLE IF NOT EXISTS confessions @@ -50,6 +70,16 @@ def createConfessionsTable(self): cur.close() conn.close() + def addAppeal(self, appeal:str, appealTitle:str, appealerId: int, appealerName:str) -> None: + self.createAppealsTable() + conn,cur = self.connectCursor() + rowVals = (appeal, appealTitle, appealerId, appealerName, time.time()) + exeString = f'''INSERT INTO appeals (appeal,appeal_title, appealer_id, appealer_name, date_added) VALUES (?,?,?,?,?)''' + cur.execute(exeString,rowVals) + conn.commit() + cur.close() + conn.close() + def addConfession(self, confession:str, title:str) -> None: self.createConfessionsTable() conn,cur = self.connectCursor() @@ -78,6 +108,24 @@ def getUnreviewedConfession(self): self.setConfessionDateReviewed(confessionId) return confessionId, confession, title + def getUnreviewedAppeal(self): + self.createAppealsTable() + appealId = 0 + appeal = "" + title = "" + conn,cur = self.connectCursor() + exeString = '''SELECT appeal_id, appeal,appeal_title FROM appeals WHERE date_reviewed IS NULL LIMIT 1''' + cur.execute(exeString) + values = cur.fetchall() + if values: + appealId = values[0][0] + appeal = values[0][1] + title = values[0][2] + cur.close() + conn.close() + self.setAppealDateReviewed(appealId) + return appealId, appeal, title + def setConfessionDateReviewed(self, confessionId): self.createConfessionsTable() conn, cur = self.connectCursor() @@ -87,6 +135,15 @@ def setConfessionDateReviewed(self, confessionId): cur.close() conn.close() + def setAppealDateReviewed(self, appealId): + self.createAppealsTable() + conn, cur = self.connectCursor() + exeString = f'''UPDATE appeals SET date_reviewed={time.time()} WHERE appeal_id={appealId} ''' + cur.execute(exeString) + conn.commit() + cur.close() + conn.close() + def reviewConfession(self, confessionId: int, approveDeny: int, reviewerId: int, reviewerName: str): self.createConfessionsTable() conn, cur = self.connectCursor() @@ -97,7 +154,17 @@ def reviewConfession(self, confessionId: int, approveDeny: int, reviewerId: int, cur.close() conn.close() - def getUnfinishedReviews(self): + def reviewAppeal(self, appealId: int, approveDeny: int, reviewerId: int, reviewerName: str): + self.createAppealsTable() + conn, cur = self.connectCursor() + values = (approveDeny, reviewerId, reviewerName, time.time(), appealId) + exeString = f'''UPDATE appeals SET appeal_status=?, reviewer_id=?, reviewer_name=?, date_reviewed=? WHERE appeal_id=? ''' + cur.execute(exeString,values) + conn.commit() + cur.close() + conn.close() + + def getUnfinishedConfessionReviews(self): self.createConfessionsTable() conn, cur = self.connectCursor() exeString = f'''SELECT confession_id, date_reviewed FROM confessions WHERE review_status IS NULL AND date_reviewed IS NOT NULL ''' @@ -107,6 +174,25 @@ def getUnfinishedReviews(self): conn.close() return value + def getUnfinishedAppealReviews(self): + self.createAppealsTable() + conn, cur = self.connectCursor() + exeString = f'''SELECT appeal_id, date_reviewed FROM appeals WHERE appeal_status IS NULL AND date_reviewed IS NOT NULL ''' + cur.execute(exeString) + value = cur.fetchall() + cur.close() + conn.close() + return value + + def resetAppealDateReviewed(self, appealId): + self.createAppealsTable() + conn, cur = self.connectCursor() + exeString = f'''UPDATE appeals SET date_reviewed=NULL WHERE appeal_id={appealId} ''' + cur.execute(exeString) + conn.commit() + cur.close() + conn.close() + def resetConfessionDateReviewed(self, confessionId): self.createConfessionsTable() conn, cur = self.connectCursor() @@ -116,7 +202,7 @@ def resetConfessionDateReviewed(self, confessionId): cur.close() conn.close() - def getAllUnreviewed(self): + def getAllUnreviewedConfessions(self): self.createConfessionsTable() conn,cur = self.connectCursor() exeString = '''SELECT confession_id, confession,confession_title FROM confessions WHERE date_reviewed IS NULL''' @@ -126,6 +212,16 @@ def getAllUnreviewed(self): conn.close() return values + def getAllUnreviewedAppeals(self): + self.createAppealsTable() + conn,cur = self.connectCursor() + exeString = '''SELECT appeal_id, appeal, appeal_title FROM appeals WHERE date_reviewed IS NULL''' + cur.execute(exeString) + values = cur.fetchall() + cur.close() + conn.close() + return values + # Platform_Accounts Table Methods def getPlatformTempTitle(self,platform, accountName): self.checkAddTitleCols() diff --git a/utils/MiruViews.py b/utils/MiruViews.py index 8644655..fca5e2c 100644 --- a/utils/MiruViews.py +++ b/utils/MiruViews.py @@ -45,6 +45,41 @@ async def denyAndReview(self, button: miru.Button, ctx: miru.ViewContext) -> Non self.db.reviewConfession(self.confessionId,0,ctx.member.id,ctx.member.display_name) self.stop() await commands.confessReview(self.tanCtx, self.rest) +class AppealReView(miru.View): + def __init__(self, *, timeout: float | int | timedelta | None = 120, autodefer: bool = True, appealId, tanCtx: tanjun.abc.SlashContext, appeal:str,rest: hikari.impl.RESTClientImpl,title) -> None: + self.db = Database() + self.appealId = appealId + self.tanCtx = tanCtx + self.appeal = appeal + self.rest = rest + self.title = title + super().__init__(timeout=timeout, autodefer=autodefer) + @miru.button(label="Approve&Finish", style=hikari.ButtonStyle.SUCCESS) + async def approveButton(self, button: miru.Button, ctx: miru.ViewContext) -> None: + self.db.reviewAppeal(self.appealId,1,ctx.member.id,ctx.member.display_name) + content = f"## {self.appealId}:{self.title}\n``` {self.appeal} ```\n<@&{Constants.MOD_ROLE_ID}> Ban appeal approved. Please react to this message to show unban action has been carried out" + await self.rest.create_message(channel=Constants.APPEAL_CHANNEL_ID, content=content, role_mentions=True) + self.stop() + @miru.button(label="Approve&Next", style=hikari.ButtonStyle.SUCCESS) + async def approveAndReview(self, button: miru.Button, ctx: miru.ViewContext) -> None: + self.db.reviewAppeal(self.appealId,1,ctx.member.id,ctx.member.display_name) + content = f"## {self.appealId}:{self.title}\n``` {self.appeal} ```\n<@&{Constants.MOD_ROLE_ID}> Ban appeal approved. Please react to this message to show unban action has been carried out" + await self.rest.create_message(channel=Constants.APPEAL_CHANNEL_ID, content=content, role_mentions=True) + self.stop() + await commands.appealReview(self.tanCtx, self.rest) + @miru.button(label="Pass&Next", style=hikari.ButtonStyle.SECONDARY) + async def passAndReview(self, button: miru.Button, ctx: miru.ViewContext) -> None: + self.stop() + await commands.appealReview(self.tanCtx, self.rest) + @miru.button(label="Deny&Finish", style=hikari.ButtonStyle.DANGER) + async def denyButton(self, button: miru.Button, ctx: miru.ViewContext) -> None: + self.db.reviewAppeal(self.appealId,0,ctx.member.id,ctx.member.display_name) + self.stop() + @miru.button(label="Deny&Next", style=hikari.ButtonStyle.DANGER) + async def denyAndReview(self, button: miru.Button, ctx: miru.ViewContext) -> None: + self.db.reviewAppeal(self.appealId,0,ctx.member.id,ctx.member.display_name) + self.stop() + await commands.appealReview(self.tanCtx, self.rest) class ConfessionModal(miru.Modal): confTitle = miru.TextInput(label="Title", placeholder="Enter your title!", required=True,max_length=75) @@ -54,6 +89,14 @@ async def callback(self, ctx: miru.ModalContext) -> None: db.addConfession(self.confess.value, self.confTitle.value) await ctx.respond(f"Submitted Confession: \n```{self.confess.value}```\n This will be posted once it's reveiwed by a mod", flags=hikari.MessageFlag.EPHEMERAL) +class AppealModal(miru.Modal): + appealTitle = miru.TextInput(label="Username", placeholder="Enter username and platform you wish to be unbanned on", required=True,max_length=75) + appeal = miru.TextInput(label="Ban Appeal",placeholder="Enter your ban appeal here", style=hikari.TextInputStyle.PARAGRAPH, required=True, max_length=1900) + async def callback(self, ctx: miru.ModalContext) -> None: + db = Database() + db.addAppeal(self.appeal.value, self.appealTitle.value, ctx.member.id, ctx.member.display_name) + await ctx.respond(f"Submitted Ban Appeal: \n```{self.appeal.value}```\n This will likely be reviewed on stream for content", flags=hikari.MessageFlag.EPHEMERAL) + class ConfessionModalView(miru.View): @miru.button(label="Click to Submit", style=hikari.ButtonStyle.PRIMARY) async def modal_button(self, button: miru.Button, ctx: miru.ViewContext) -> None: @@ -61,6 +104,13 @@ async def modal_button(self, button: miru.Button, ctx: miru.ViewContext) -> None # await ctx.respond_with_modal(modal) await modal.send(ctx.interaction) +class AppealModalView(miru.View): + @miru.button(label="Click to Submit", style=hikari.ButtonStyle.PRIMARY) + async def modal_button(self, button: miru.Button, ctx: miru.ViewContext) -> None: + modal = AppealModal(title="Submit Ban Appeal") + # await ctx.respond_with_modal(modal) + await modal.send(ctx.interaction) + def createConfessionEmbed(confessionId, confession, title): color = random.randrange(0, 2**24) color = hex(color) diff --git a/utils/StaticMethods.py b/utils/StaticMethods.py index 35b55b5..1bd3418 100644 --- a/utils/StaticMethods.py +++ b/utils/StaticMethods.py @@ -27,12 +27,20 @@ def logCommand(funcName, ctx) -> None: def resetUnfinishedConfessions(): db = Database() - unFinished = db.getUnfinishedReviews() + unFinished = db.getUnfinishedConfessionReviews() if unFinished: for row in unFinished: if timeToSeconds(row[1]) >= Constants.TIME_BEFORE_REVIEW_RESET: db.resetConfessionDateReviewed(row[0]) +def resetUnfinishedAppeals(): + db = Database() + unFinished = db.getUnfinishedAppealReviews() + if unFinished: + for row in unFinished: + if timeToSeconds(row[1]) >= Constants.TIME_BEFORE_REVIEW_RESET: + db.resetAppealDateReviewed(row[0]) + async def isPermission(ctx: tanjun.abc.SlashContext)-> bool: hasPermission = False roles = ctx.member.get_roles()