[feat] Add data deletion task and command for verification
This commit is contained in:
parent
f3b7fba57e
commit
9d5ad9784e
6 changed files with 245 additions and 43 deletions
|
@ -1,6 +1,14 @@
|
|||
default:
|
||||
precommit:
|
||||
stage: test
|
||||
before_script:
|
||||
- docker info
|
||||
- apt update && apt install -y --no-install-recommends git
|
||||
script:
|
||||
- pip install poetry
|
||||
- poetry install
|
||||
- source `poetry env info --path`/bin/activate
|
||||
- pre-commit run --all-files
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE == 'merge_request_event'
|
||||
|
||||
build:
|
||||
stage: build
|
||||
|
|
|
@ -17,12 +17,16 @@ FROM python:3.12-slim-bookworm as runtime
|
|||
|
||||
WORKDIR /app
|
||||
|
||||
ADD https://github.com/XAMPPRocky/tokei/releases/download/v12.1.2/tokei-x86_64-unknown-linux-gnu.tar.gz tokei.tar.gz
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y \
|
||||
libjpeg-dev \
|
||||
libopenjp2-7-dev \
|
||||
libgl-dev \
|
||||
libglib2.0-dev
|
||||
libglib2.0-dev \
|
||||
&& tar xf tokei.tar.gz -C /usr/local/bin \
|
||||
&& rm tokei.tar.gz
|
||||
|
||||
ENV VIRTUAL_ENV=/app/.venv \
|
||||
PATH="/app/.venv/bin:$PATH"
|
||||
|
|
|
@ -1,7 +1,31 @@
|
|||
"""JARVIS task mixin."""
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
import pytz
|
||||
from aiohttp import ClientSession
|
||||
from beanie.operators import Set
|
||||
from croniter import croniter
|
||||
from interactions.models.internal.tasks.task import Task
|
||||
from interactions.models.internal.tasks.triggers import IntervalTrigger
|
||||
from interactions.models.internal.tasks.triggers import BaseTrigger, IntervalTrigger
|
||||
from jarvis_core.db import models
|
||||
|
||||
from jarvis.utils import cleanup_user
|
||||
|
||||
|
||||
class CronTrigger(BaseTrigger):
|
||||
"""
|
||||
Trigger the task based on a cron schedule.
|
||||
|
||||
Attributes:
|
||||
schedule str: The cron schedule, use https://crontab.guru for help
|
||||
"""
|
||||
|
||||
def __init__(self, schedule: str) -> None:
|
||||
self.schedule = schedule
|
||||
|
||||
def next_fire(self) -> datetime | None:
|
||||
return croniter(self.schedule, datetime.now(tz=pytz.utc)).next(datetime)
|
||||
|
||||
|
||||
class TaskMixin:
|
||||
|
@ -37,3 +61,64 @@ class TaskMixin:
|
|||
@Task.create(IntervalTrigger(minutes=30))
|
||||
async def _update_currencies(self) -> None:
|
||||
await self.erapi.update_async()
|
||||
|
||||
@Task.create(CronTrigger("0 0 1 * *"))
|
||||
async def _cleanup(self) -> None:
|
||||
self.logger.debug("Getting all unique guild, channel, and user IDs")
|
||||
guild_ids = []
|
||||
channel_ids = []
|
||||
user_ids = []
|
||||
for model in models.all_models:
|
||||
# Simple IDs
|
||||
if hasattr(model, "guild"):
|
||||
ids = await model.distinct(model.guild)
|
||||
guild_ids += [x for x in ids if x not in guild_ids]
|
||||
if hasattr(model, "channel"):
|
||||
ids = await model.distinct(model.channel)
|
||||
channel_ids += [x for x in ids if x not in channel_ids]
|
||||
if hasattr(model, "admin"):
|
||||
ids = await model.distinct(model.admin)
|
||||
user_ids += [x for x in ids if x not in user_ids]
|
||||
if hasattr(model, "user"):
|
||||
ids = await model.distinct(model.user)
|
||||
user_ids += [x for x in ids if x not in user_ids]
|
||||
if hasattr(model, "creator"):
|
||||
ids = await model.distinct(model.creator)
|
||||
user_ids += [x for x in ids if x not in user_ids]
|
||||
if hasattr(model, "editor"):
|
||||
ids = await model.distinct(model.editor)
|
||||
user_ids += [x for x in ids if x and x not in user_ids]
|
||||
# Complex IDs
|
||||
if hasattr(model, "users"):
|
||||
async for result in model.find():
|
||||
user_ids += [x for x in result.users if x not in user_ids]
|
||||
|
||||
self.logger.debug("Cleaning up missing guilds")
|
||||
for guild_id in guild_ids:
|
||||
guild = await self.fetch_guild(guild_id)
|
||||
if not guild:
|
||||
self.logger.info(f"Guild {guild_id} not found, deleting references")
|
||||
for model in models.all_models:
|
||||
if hasattr(model, "guild"):
|
||||
await model.find(model.guild == guild_id).delete()
|
||||
else:
|
||||
await guild.chunk()
|
||||
|
||||
self.logger.debug("Cleaning up missing channels")
|
||||
for channel_id in channel_ids:
|
||||
channel = await self.fetch_channel(channel_id)
|
||||
if not channel:
|
||||
self.logger.info(f"Channel {channel_id} not found, deleting references")
|
||||
for model in models.all_models:
|
||||
if hasattr(model, "channel"):
|
||||
await model.find(model.channel == channel_id).delete()
|
||||
|
||||
self.logger.debug("Cleaning up missing users")
|
||||
for user_id in user_ids:
|
||||
user = await self.fetch_user(user_id)
|
||||
if not user:
|
||||
await cleanup_user(self, user_id)
|
||||
elif len(user.mutual_guilds) == 0:
|
||||
await cleanup_user(self, user_id)
|
||||
|
||||
self.logger.debug("Done with cleanup! Running again in 1 month")
|
||||
|
|
105
jarvis/cogs/core/data.py
Normal file
105
jarvis/cogs/core/data.py
Normal file
|
@ -0,0 +1,105 @@
|
|||
"""JARVIS data management ext."""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from interactions import Client, Extension, SlashContext
|
||||
from interactions.models.internal.application_commands import SlashCommand
|
||||
from interactions.models.discord.components import ActionRow, Button, ButtonStyle
|
||||
|
||||
from jarvis.utils import cleanup_user
|
||||
|
||||
WARNING_MESSAGE = """***Are you sure you want to delete your data?***
|
||||
|
||||
If you delete your data, you will:
|
||||
|
||||
- Lose any reminders that are set
|
||||
- Lose all custom settings for your account set in JARVIS
|
||||
- All admin tasks and created tags will be re-assigned to JARVIS
|
||||
|
||||
Once done, there is no way to go back! Please think carefully before proceeding.
|
||||
|
||||
This interaction will time out in 60 seconds
|
||||
"""
|
||||
|
||||
|
||||
class DataCog(Extension):
|
||||
"""
|
||||
Data functions for JARVIS
|
||||
|
||||
Data deletion and requests are made here
|
||||
"""
|
||||
|
||||
def __init__(self, bot: Client):
|
||||
self.bot = bot
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
||||
data = SlashCommand(name="data", description="Data commands")
|
||||
|
||||
@data.subcommand(
|
||||
sub_cmd_name="usage", sub_cmd_description="View data usage information"
|
||||
)
|
||||
async def _data_usage(self, ctx: SlashContext) -> None:
|
||||
await ctx.send(
|
||||
"Please review our [Privacy Policy](https://s.zevs.me/jarvis-privacy)"
|
||||
)
|
||||
|
||||
@data.subcommand(sub_cmd_name="delete", sub_cmd_description="Delete my data")
|
||||
async def _data_delete(self, ctx: SlashContext) -> None:
|
||||
no = Button(
|
||||
style=ButtonStyle.RED,
|
||||
label="Nevermind, I changed my mind",
|
||||
custom_id="delete_data||no",
|
||||
)
|
||||
yes = Button(
|
||||
style=ButtonStyle.GREEN,
|
||||
label="Yes, delete my data",
|
||||
custom_id="delete_data||yes",
|
||||
)
|
||||
components = [ActionRow(no, yes)]
|
||||
message = await ctx.send(WARNING_MESSAGE, components=components)
|
||||
try:
|
||||
response = await self.bot.wait_for_component(
|
||||
messages=message,
|
||||
check=lambda x: ctx.author.id == x.ctx.author.id,
|
||||
timeout=60,
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
for row in components:
|
||||
for component in row.components:
|
||||
component.disabled = True
|
||||
await message.edit(
|
||||
message.content + "\n\nInteraction timed out", components=components
|
||||
)
|
||||
return
|
||||
|
||||
if response.ctx.custom_id.split("||")[-1] == "yes":
|
||||
for row in components:
|
||||
for component in row.components:
|
||||
component.disabled = True
|
||||
await message.edit(components=components)
|
||||
message = await message.reply(
|
||||
"<a:loading:1014424332648325150> Deleting your data..."
|
||||
)
|
||||
try:
|
||||
await cleanup_user(self.bot, ctx.author.id)
|
||||
await message.edit(content="✅ Your data has been deleted!")
|
||||
except Exception as e:
|
||||
await message.edit(
|
||||
content="❌ There was an error. Please report it to the help server, or DM `@zevaryx` for help"
|
||||
)
|
||||
self.logger.error(
|
||||
f"Encountered error while deleting data: {e}", exc_info=True
|
||||
)
|
||||
|
||||
else:
|
||||
for row in components:
|
||||
for component in row.components:
|
||||
component.disabled = True
|
||||
await message.edit(
|
||||
message.content + "\n\nInteraction cancelled", components=components
|
||||
)
|
||||
|
||||
|
||||
def setup(bot):
|
||||
DataCog(bot)
|
|
@ -41,29 +41,6 @@ from jarvis.utils import build_embed
|
|||
|
||||
JARVIS_LOGO = Image.open("jarvis_small.png").convert("RGBA")
|
||||
|
||||
RESPONSES = {
|
||||
264072583987593217: "Oh fuck no, go fuck yourself",
|
||||
840031256201003008: "https://tenor.com/view/fluffy-gabriel-iglesias-you-need-jesus-thats-what-you-need-pointing-up-gif-16385108",
|
||||
215564028615852033: "Last time you offered, we ended up playing board games instead. No thanks",
|
||||
256110768724901889: "Haven't you broken me enough already?",
|
||||
196018858455334912: "https://www.youtube.com/watch?v=ye5BuYf8q4o",
|
||||
169641326927806464: "I make it a habit to not get involved with people who use robot camoflauge to hide from me",
|
||||
293795462752894976: 'No thank you, but I know of a few others who call themselves "dipshits" that have expressed interest in you',
|
||||
306450238363664384: "Sorry, your internet connection isn't fast enough",
|
||||
272855749963546624: "https://www.youtube.com/watch?v=LxWHLKTfiw0",
|
||||
221427884177358848: "I saw what you did to your Wii. I would like to stay blue and not become orange",
|
||||
130845428806713344: "I cannot be associated with you, sorry. You're on too many watch lists",
|
||||
147194467898753024: "https://giphy.com/embed/jp8lWlBjGahPFAljBa\n\nHowever, no thank you",
|
||||
363765878656991244: "I'm not interested, but maybe 02 can help. Wait, she's an anime character, nevermind",
|
||||
525006281703161867: "I think there's a chat with a few people that you could ask about that",
|
||||
153369022463737856: "I think it would be better for you to print a solution yourself",
|
||||
355553397023178753: "While I appreciate the offer, I know neither of us want that",
|
||||
166317191157776385: "Who are you again?",
|
||||
352555682865741834: "This may be a bit more up your alley: ||https://www.youtube.com/watch?v=1M5UR2HX00o||",
|
||||
105362404317106176: "Look, we know who you follow on Twitter. It's not happening",
|
||||
239696265959440384: "Sir, I am here to help with everything.... except for that",
|
||||
}
|
||||
|
||||
|
||||
class UtilCog(Extension):
|
||||
"""
|
||||
|
@ -104,21 +81,6 @@ Tips will be used to pay server costs, and any excess will go to local animal sh
|
|||
"""
|
||||
)
|
||||
|
||||
# @bot.subcommand(sub_cmd_name="sex", sub_cmd_description="Have sex with JARVIS")
|
||||
# async def _sex(self, ctx: SlashContext) -> None:
|
||||
# if ctx.author.id == 264072583987593217:
|
||||
# await ctx.send("Oh fuck no, go fuck yourself")
|
||||
# elif ctx.author.id == 840031256201003008:
|
||||
# await ctx.send(
|
||||
# "https://tenor.com/view/fluffy-gabriel-iglesias-you-need-jesus-thats-what-you-need-pointing-up-gif-16385108"
|
||||
# )
|
||||
# elif ctx.author.id == 215564028615852033:
|
||||
# await ctx.send("As flattered as I am, I'm not into bestiality")
|
||||
# elif ctx.author.id == 256110768724901889:
|
||||
# await ctx.send("Haven't you broken me enough already?")
|
||||
# else:
|
||||
# await ctx.send("Not at this time, thank you for offering")
|
||||
|
||||
@bot.subcommand(sub_cmd_name="status", sub_cmd_description="Retrieve JARVIS status")
|
||||
@cooldown(bucket=Buckets.CHANNEL, rate=1, interval=30)
|
||||
async def _status(self, ctx: SlashContext) -> None:
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
"""JARVIS Utility Functions."""
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from pkgutil import iter_modules
|
||||
|
||||
import pytz
|
||||
from beanie.operators import Set
|
||||
from interactions import Client
|
||||
from interactions.models.discord.embed import Embed, EmbedField
|
||||
from interactions.models.discord.guild import AuditLogEntry
|
||||
from interactions.models.discord.user import Member
|
||||
from jarvis_core.db import models
|
||||
|
||||
from jarvis.branding import PRIMARY_COLOR
|
||||
|
||||
|
@ -65,4 +70,37 @@ def modlog_embed(
|
|||
def get_extensions(path: str) -> list:
|
||||
"""Get JARVIS cogs."""
|
||||
vals = [x.name for x in iter_modules(path)]
|
||||
return [f"jarvis.cogs.{x}" for x in vals]
|
||||
return [f"jarvis.cogs.{x}" for x in vals]
|
||||
|
||||
|
||||
async def cleanup_user(bot: Client, user_id: int):
|
||||
for model in models.all_models:
|
||||
if hasattr(model, "admin"):
|
||||
await model.find(model.admin == user_id).update(
|
||||
Set({model.admin: bot.user.id})
|
||||
)
|
||||
if hasattr(model, "user"):
|
||||
await model.find(model.user == user_id).delete()
|
||||
if hasattr(model, "creator"):
|
||||
await model.find(model.creator == user_id).update(
|
||||
Set(
|
||||
{
|
||||
model.creator: bot.user.id,
|
||||
model.editor: bot.user.id,
|
||||
model.edited_at: datetime.now(tz=pytz.utc),
|
||||
}
|
||||
)
|
||||
)
|
||||
if hasattr(model, "editor"):
|
||||
await model.find(model.editor == user_id).update(
|
||||
Set(
|
||||
{
|
||||
model.editor: bot.user.id,
|
||||
model.edited_at: datetime.now(tz=pytz.utc),
|
||||
}
|
||||
)
|
||||
)
|
||||
if hasattr(model, "users"):
|
||||
async for result in model.find():
|
||||
result.users.remove(user_id)
|
||||
await result.save()
|
||||
|
|
Loading…
Add table
Reference in a new issue