feat(bot): Everything.
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
from tortoise import BaseDBAsyncClient
|
||||
|
||||
|
||||
async def upgrade(db: BaseDBAsyncClient) -> str:
|
||||
return """
|
||||
CREATE TABLE IF NOT EXISTS "call" (
|
||||
"id" SERIAL NOT NULL PRIMARY KEY,
|
||||
"started_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"ended_at" TIMESTAMPTZ
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "server" (
|
||||
"id" BIGSERIAL NOT NULL PRIMARY KEY,
|
||||
"prefix" VARCHAR(3) NOT NULL DEFAULT 'p.'
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "channel" (
|
||||
"id" BIGSERIAL NOT NULL PRIMARY KEY,
|
||||
"enabled" BOOL,
|
||||
"webhook_id" BIGINT,
|
||||
"webhook_url" TEXT,
|
||||
"call_id" INT REFERENCES "call" ("id") ON DELETE CASCADE,
|
||||
"server_id" BIGINT NOT NULL REFERENCES "server" ("id") ON DELETE CASCADE
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "user" (
|
||||
"id" BIGSERIAL NOT NULL PRIMARY KEY,
|
||||
"username" VARCHAR(32) NOT NULL,
|
||||
"is_masked" BOOL NOT NULL DEFAULT False,
|
||||
"is_moderator" BOOL NOT NULL DEFAULT False
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "profile" (
|
||||
"id" SERIAL NOT NULL PRIMARY KEY,
|
||||
"avatar_url" TEXT NOT NULL,
|
||||
"name" VARCHAR(32) NOT NULL,
|
||||
"color" INT,
|
||||
"server_id" BIGINT REFERENCES "server" ("id") ON DELETE CASCADE,
|
||||
"user_id" BIGINT NOT NULL REFERENCES "user" ("id") ON DELETE CASCADE,
|
||||
CONSTRAINT "uid_profile_user_id_903480" UNIQUE ("user_id", "server_id")
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "calluserchannelrelationship" (
|
||||
"id" SERIAL NOT NULL PRIMARY KEY,
|
||||
"is_masked" BOOL NOT NULL,
|
||||
"mask" BIGINT NOT NULL,
|
||||
"call_id" INT NOT NULL REFERENCES "call" ("id") ON DELETE CASCADE,
|
||||
"channel_id" BIGINT NOT NULL REFERENCES "channel" ("id") ON DELETE CASCADE,
|
||||
"profile_id" INT NOT NULL REFERENCES "profile" ("id") ON DELETE CASCADE,
|
||||
"user_id" BIGINT NOT NULL REFERENCES "user" ("id") ON DELETE CASCADE,
|
||||
CONSTRAINT "uid_callusercha_call_id_fad9b8" UNIQUE ("call_id", "user_id", "channel_id")
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "message" (
|
||||
"id" BIGSERIAL NOT NULL PRIMARY KEY,
|
||||
"mask" BIGINT,
|
||||
"was_deleted" BOOL NOT NULL DEFAULT False,
|
||||
"propagation_id" BIGINT,
|
||||
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"call_id" INT NOT NULL REFERENCES "call" ("id") ON DELETE CASCADE,
|
||||
"channel_id" BIGINT NOT NULL REFERENCES "channel" ("id") ON DELETE CASCADE,
|
||||
"profile_id" INT NOT NULL REFERENCES "profile" ("id") ON DELETE CASCADE,
|
||||
"propagation_channel_id" BIGINT REFERENCES "channel" ("id") ON DELETE CASCADE,
|
||||
"replying_to_id" BIGINT REFERENCES "message" ("id") ON DELETE SET NULL,
|
||||
"user_id" BIGINT NOT NULL REFERENCES "user" ("id") ON DELETE CASCADE
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS "idx_message_propaga_f0803c" ON "message" ("propagation_id");
|
||||
CREATE TABLE IF NOT EXISTS "messagecontent" (
|
||||
"id" SERIAL NOT NULL PRIMARY KEY,
|
||||
"content" VARCHAR(4096) NOT NULL,
|
||||
"was_propagated" BOOL NOT NULL DEFAULT True,
|
||||
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"message_id" BIGINT NOT NULL REFERENCES "message" ("id") ON DELETE CASCADE
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "report" (
|
||||
"id" SERIAL NOT NULL PRIMARY KEY,
|
||||
"reason" VARCHAR(500) NOT NULL,
|
||||
"priority" INT NOT NULL,
|
||||
"status" SMALLINT NOT NULL DEFAULT 2,
|
||||
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"reported_by_id" BIGINT REFERENCES "user" ("id") ON DELETE CASCADE,
|
||||
"call_id" INT NOT NULL UNIQUE REFERENCES "call" ("id") ON DELETE CASCADE
|
||||
);
|
||||
COMMENT ON COLUMN "report"."status" IS 'ACCEPTED: 0\nDECLINED: 1\nON_REVIEW: 2';
|
||||
CREATE TABLE IF NOT EXISTS "ban" (
|
||||
"id" SERIAL NOT NULL PRIMARY KEY,
|
||||
"notification_message_id" BIGINT,
|
||||
"reason" VARCHAR(500) NOT NULL,
|
||||
"appeal" VARCHAR(500),
|
||||
"status" SMALLINT NOT NULL DEFAULT 0,
|
||||
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"expires_at" TIMESTAMPTZ,
|
||||
"banned_by_id" BIGINT NOT NULL REFERENCES "user" ("id") ON DELETE CASCADE,
|
||||
"report_id" INT REFERENCES "report" ("id") ON DELETE SET NULL,
|
||||
"user_id" BIGINT NOT NULL REFERENCES "user" ("id") ON DELETE NO ACTION
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS "idx_ban_notific_ea7b58" ON "ban" ("notification_message_id");
|
||||
CREATE INDEX IF NOT EXISTS "idx_ban_user_id_37a9cf" ON "ban" ("user_id", "expires_at");
|
||||
COMMENT ON COLUMN "ban"."status" IS 'VALID: 0\nON_REVIEW: 1\nAPPEAL_ACCEPTED: 2\nAPPEAL_DECLINED: 3';
|
||||
CREATE TABLE IF NOT EXISTS "aerich" (
|
||||
"id" SERIAL NOT NULL PRIMARY KEY,
|
||||
"version" VARCHAR(255) NOT NULL,
|
||||
"app" VARCHAR(100) NOT NULL,
|
||||
"content" JSONB NOT NULL
|
||||
);"""
|
||||
|
||||
|
||||
async def downgrade(db: BaseDBAsyncClient) -> str:
|
||||
return """
|
||||
"""
|
||||
+211
-34
@@ -4,23 +4,42 @@ import enum
|
||||
|
||||
from tortoise import models, fields
|
||||
|
||||
from discord import webhook
|
||||
|
||||
import aiohttp
|
||||
|
||||
from payphone.core import config, constants
|
||||
|
||||
|
||||
class User(models.Model):
|
||||
id: int = fields.BigIntField(primary_key=True)
|
||||
username: str = fields.CharField(max_length=32)
|
||||
is_masked: bool = fields.BooleanField(default=False)
|
||||
is_moderator: bool = fields.BooleanField(default=False)
|
||||
profiles: fields.BackwardFKRelation["Profile"]
|
||||
call_rss: fields.BackwardFKRelation["CallUserChannelRelationship"]
|
||||
|
||||
|
||||
class Call(models.Model):
|
||||
started_at = fields.DatetimeField(auto_now_add=True)
|
||||
ended_at = fields.DatetimeField(null=True, default=None)
|
||||
|
||||
channels: fields.BackwardFKRelation["Channel"]
|
||||
call_rss: fields.BackwardFKRelation["CallUserChannelRelationship"]
|
||||
report: fields.BackwardOneToOneRelation["Report"]
|
||||
|
||||
|
||||
class Profile(models.Model):
|
||||
user: fields.ForeignKeyRelation["User"] = fields.ForeignKeyField(
|
||||
"models.User", related_name="profiles"
|
||||
"models.User", related_name="profiles", on_delete=fields.OnDelete.CASCADE
|
||||
)
|
||||
server: fields.ForeignKeyRelation["Server"] = fields.ForeignKeyField(
|
||||
"models.Server", related_name="profiles"
|
||||
server: fields.ForeignKeyNullableRelation["Server"] = fields.ForeignKeyField(
|
||||
"models.Server",
|
||||
related_name="profiles",
|
||||
null=True,
|
||||
on_delete=fields.OnDelete.CASCADE,
|
||||
)
|
||||
image_url: str = fields.TextField()
|
||||
avatar_url: str = fields.TextField()
|
||||
name: str = fields.CharField(max_length=32)
|
||||
color: int | None = fields.IntField(null=True, default=None)
|
||||
|
||||
@@ -32,6 +51,7 @@ class Profile(models.Model):
|
||||
|
||||
class Server(models.Model):
|
||||
id: int = fields.BigIntField(primary_key=True)
|
||||
prefix: str = fields.CharField(max_length=3, default="p.")
|
||||
channels: fields.BackwardFKRelation["Channel"]
|
||||
profiles: fields.BackwardFKRelation["Profile"]
|
||||
|
||||
@@ -39,32 +59,105 @@ class Server(models.Model):
|
||||
class Channel(models.Model):
|
||||
id = fields.BigIntField(primary_key=True)
|
||||
server: fields.ForeignKeyRelation[Server] = fields.ForeignKeyField(
|
||||
"models.Server", related_name="channels"
|
||||
"models.Server", related_name="channels", on_delete=fields.OnDelete.CASCADE
|
||||
)
|
||||
is_nsfw = fields.BooleanField()
|
||||
call = fields.ForeignKeyField("models.Call", null=True, related_name="channels")
|
||||
webhook_id = fields.BigIntField(null=True)
|
||||
webhook_url = fields.TextField()
|
||||
call: fields.ForeignKeyNullableRelation["Call"] = fields.ForeignKeyField(
|
||||
"models.Call", null=True, related_name="channels"
|
||||
)
|
||||
enabled: bool | None = fields.BooleanField(null=True, default=None)
|
||||
webhook_id: int | None = fields.BigIntField(null=True)
|
||||
webhook_url: str | None = fields.TextField(null=True)
|
||||
|
||||
call_rss: fields.BackwardFKRelation["CallUserChannelRelationship"]
|
||||
messages: fields.BackwardFKRelation["Message"]
|
||||
propagated_messages: fields.BackwardFKRelation["Message"]
|
||||
|
||||
def webhook(self, session: aiohttp.ClientSession) -> webhook.Webhook | None:
|
||||
"""Initializes the channel's webhook instance.
|
||||
|
||||
class Call(models.Model):
|
||||
is_nsfw = fields.BooleanField()
|
||||
started_at = fields.DatetimeField(auto_now_add=True)
|
||||
ended_at = fields.DatetimeField(null=True, default=None)
|
||||
Args:
|
||||
session (aiohttp.ClientSession): An initialized aiohttp client session.
|
||||
|
||||
channels: fields.BackwardFKRelation["Channel"]
|
||||
call_rss: fields.BackwardFKRelation["CallUserChannelRelationship"]
|
||||
Returns:
|
||||
webhook.Webhook | None: The webhook, or None if not initialized.
|
||||
"""
|
||||
|
||||
if self.webhook_url is None:
|
||||
return None
|
||||
|
||||
return webhook.Webhook.from_url(
|
||||
self.webhook_url, session=session, bot_token=config.TOKEN
|
||||
)
|
||||
|
||||
class Meta:
|
||||
index = [("server_id", "enabled")]
|
||||
|
||||
|
||||
class CallUserChannelRelationship(models.Model):
|
||||
call = fields.ForeignKeyField("models.Call", related_name="call_rss")
|
||||
user = fields.ForeignKeyField("models.User", related_name="call_rss")
|
||||
profile = fields.ForeignKeyField("models.Profile", related_name="call_rss")
|
||||
channel = fields.ForeignKeyField("models.Channel", related_name="call_rss")
|
||||
is_masked = fields.BooleanField()
|
||||
mask = fields.BigIntField()
|
||||
call: fields.ForeignKeyRelation["Call"] = fields.ForeignKeyField(
|
||||
"models.Call", related_name="call_rss", on_delete=fields.OnDelete.CASCADE
|
||||
)
|
||||
user: fields.ForeignKeyRelation["User"] = fields.ForeignKeyField(
|
||||
"models.User", related_name="call_rss", on_delete=fields.OnDelete.CASCADE
|
||||
)
|
||||
profile: fields.ForeignKeyRelation["Profile"] = fields.ForeignKeyField(
|
||||
"models.Profile", related_name="call_rss", on_delete=fields.OnDelete.CASCADE
|
||||
)
|
||||
channel: fields.ForeignKeyRelation["Channel"] = fields.ForeignKeyField(
|
||||
"models.Channel", related_name="call_rss", on_delete=fields.OnDelete.CASCADE
|
||||
)
|
||||
is_masked: bool = fields.BooleanField()
|
||||
mask: int = fields.BigIntField()
|
||||
|
||||
@property
|
||||
def mask_name(self) -> str:
|
||||
"""Returns the mask's name.
|
||||
|
||||
Returns:
|
||||
str: The mask's name.
|
||||
"""
|
||||
|
||||
return constants.Mask(self.mask).name.replace("_", " ").lower().title()
|
||||
|
||||
@property
|
||||
def display_name(self) -> str:
|
||||
"""Returns the displayed name.
|
||||
|
||||
Returns:
|
||||
str: The displayed name.
|
||||
"""
|
||||
|
||||
if self.is_masked:
|
||||
return self.mask_name
|
||||
|
||||
return self.profile.name
|
||||
|
||||
@property
|
||||
def display_avatar(self) -> str:
|
||||
"""Returns the displayed avatar URL.
|
||||
|
||||
Returns:
|
||||
str: The displayed avatar URL.
|
||||
"""
|
||||
|
||||
if self.is_masked:
|
||||
mask = constants.Mask(self.mask)
|
||||
return f"https://payphone.nyeki.dev/assets/masks/{mask.name.lower().replace("_", "-")}.png"
|
||||
|
||||
return self.profile.avatar_url
|
||||
|
||||
@property
|
||||
def mask_emoji(self) -> str:
|
||||
"""Returns the mask's emoji.
|
||||
|
||||
Returns:
|
||||
str: The mask's emoji (<:name:id>).
|
||||
"""
|
||||
|
||||
mask = constants.Mask(self.mask)
|
||||
mask_name = mask.name.lower().replace("_", "")
|
||||
|
||||
return f"<:{mask_name}:{self.mask}>"
|
||||
|
||||
class Meta:
|
||||
unique_together = [("call_id", "user_id", "channel_id")]
|
||||
@@ -72,19 +165,91 @@ class CallUserChannelRelationship(models.Model):
|
||||
|
||||
class Message(models.Model):
|
||||
id = fields.BigIntField(primary_key=True)
|
||||
channel = fields.ForeignKeyField("models.Channel")
|
||||
user = fields.ForeignKeyField("models.User")
|
||||
profile = fields.ForeignKeyField("models.Profile")
|
||||
call: fields.ForeignKeyRelation["Call"] = fields.ForeignKeyField(
|
||||
"models.Call", related_name="messages", on_delete=fields.OnDelete.CASCADE
|
||||
)
|
||||
channel: fields.ForeignKeyRelation["Channel"] = fields.ForeignKeyField(
|
||||
"models.Channel", related_name="messages", on_delete=fields.OnDelete.CASCADE
|
||||
)
|
||||
user = fields.ForeignKeyRelation("models.User", on_delete=fields.OnDelete.CASCADE)
|
||||
profile = fields.ForeignKeyField(
|
||||
"models.Profile", on_delete=fields.OnDelete.CASCADE
|
||||
)
|
||||
replying_to: fields.ForeignKeyNullableRelation["Message"] = fields.ForeignKeyField(
|
||||
"models.Message",
|
||||
related_name="replies",
|
||||
on_delete=fields.OnDelete.SET_NULL,
|
||||
null=True,
|
||||
default=None,
|
||||
)
|
||||
mask = fields.BigIntField(null=True)
|
||||
was_deleted = fields.BooleanField(default=False)
|
||||
was_propagated = fields.BooleanField(default=False)
|
||||
propagation_id = fields.BigIntField(null=True, db_index=True)
|
||||
propagation_channel: fields.ForeignKeyNullableRelation["Channel"] = (
|
||||
fields.ForeignKeyField(
|
||||
"models.Channel", related_name="propagated_messages", null=True
|
||||
)
|
||||
)
|
||||
created_at = fields.DatetimeField(auto_now_add=True)
|
||||
contents: fields.BackwardFKRelation["MessageContent"]
|
||||
|
||||
@property
|
||||
def mask_name(self) -> str:
|
||||
"""Returns the mask's name.
|
||||
|
||||
Returns:
|
||||
str: The mask's name.
|
||||
"""
|
||||
|
||||
return constants.Mask(self.mask).name.replace("_", " ").lower().title()
|
||||
|
||||
@property
|
||||
def display_name(self) -> str:
|
||||
"""Returns the displayed name.
|
||||
|
||||
Returns:
|
||||
str: The displayed name.
|
||||
"""
|
||||
|
||||
if self.mask is not None:
|
||||
return self.mask_name
|
||||
|
||||
return self.profile.name
|
||||
|
||||
@property
|
||||
def display_avatar(self) -> str:
|
||||
"""Returns the displayed avatar URL.
|
||||
|
||||
Returns:
|
||||
str: The displayed avatar URL.
|
||||
"""
|
||||
|
||||
if self.mask is not None:
|
||||
mask = constants.Mask(self.mask)
|
||||
return f"https://payphone.nyeki.dev/assets/masks/{mask.name.lower().replace("_", "-")}.png"
|
||||
|
||||
return self.profile.avatar_url
|
||||
|
||||
@property
|
||||
def mask_emoji(self) -> str:
|
||||
"""Returns the mask's emoji.
|
||||
|
||||
Returns:
|
||||
str: The mask's emoji (<:name:id>).
|
||||
"""
|
||||
|
||||
mask = constants.Mask(self.mask)
|
||||
mask_name = mask.name.lower().replace("_", "")
|
||||
|
||||
return f"<:{mask_name}:{self.mask}>"
|
||||
|
||||
|
||||
class MessageContent(models.Model):
|
||||
message = fields.ForeignKeyField("models.Message", related_name="contents")
|
||||
message = fields.ForeignKeyField(
|
||||
"models.Message", related_name="contents", on_delete=fields.OnDelete.CASCADE
|
||||
)
|
||||
content = fields.CharField(max_length=4096)
|
||||
was_propagated = fields.BooleanField()
|
||||
was_propagated = fields.BooleanField(default=True)
|
||||
created_at = fields.DatetimeField(auto_now_add=True)
|
||||
|
||||
|
||||
@@ -95,29 +260,41 @@ class ReportStatus(enum.IntEnum):
|
||||
|
||||
|
||||
class Report(models.Model):
|
||||
call: fields.ForeignKeyRelation["Call"] = fields.ForeignKeyField("models.Call")
|
||||
call: fields.OneToOneRelation["Call"] = fields.OneToOneField(
|
||||
"models.Call", related_name="report"
|
||||
)
|
||||
reported_by: fields.ForeignKeyNullableRelation["User"] = fields.ForeignKeyField(
|
||||
"users.User", null=True
|
||||
"models.User", null=True, on_delete=fields.OnDelete.CASCADE
|
||||
)
|
||||
reason: str = fields.CharField(max_length=500)
|
||||
priority: int = fields.IntField()
|
||||
status: ReportStatus = fields.IntEnumField(ReportStatus)
|
||||
status: ReportStatus = fields.IntEnumField(
|
||||
ReportStatus, default=ReportStatus.ON_REVIEW
|
||||
)
|
||||
created_at: datetime = fields.DatetimeField(auto_now_add=True)
|
||||
|
||||
|
||||
class BanStatus(enum.IntEnum):
|
||||
VALID = 0
|
||||
APPEALED = 1
|
||||
ON_REVIEW = 1
|
||||
APPEAL_ACCEPTED = 2
|
||||
APPEAL_DECLINED = 3
|
||||
|
||||
|
||||
class Ban(models.Model):
|
||||
user: fields.ForeignKeyRelation["User"] = fields.ForeignKeyField(
|
||||
"models.User", related_name="bans"
|
||||
"models.User", related_name="bans", on_delete=fields.OnDelete.NO_ACTION
|
||||
)
|
||||
notification_message_id: int = fields.BigIntField(null=True)
|
||||
report: fields.ForeignKeyNullableRelation["Report"] = fields.ForeignKeyField(
|
||||
"models.Report",
|
||||
related_name="bans",
|
||||
null=True,
|
||||
on_delete=fields.OnDelete.SET_NULL,
|
||||
)
|
||||
notification_message_id: int = fields.BigIntField(null=True, db_index=True)
|
||||
reason: str = fields.CharField(max_length=500)
|
||||
appeal: str | None = fields.CharField(max_length=500, null=True)
|
||||
status: BanStatus = fields.IntEnumField(BanStatus)
|
||||
status: BanStatus = fields.IntEnumField(BanStatus, default=BanStatus.VALID)
|
||||
banned_by: fields.ForeignKeyRelation["User"] = fields.ForeignKeyField(
|
||||
"models.User", related_name="bans_created"
|
||||
)
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
from datetime import datetime, timezone
|
||||
|
||||
import tortoise
|
||||
|
||||
from payphone.db import models
|
||||
from payphone.core import config
|
||||
|
||||
|
||||
async def init() -> None:
|
||||
"""Initialize the database and it's connections.
|
||||
"""
|
||||
|
||||
await tortoise.Tortoise.init(config.TORTOISE_ORM, use_tz=True)
|
||||
|
||||
await tortoise.Tortoise.generate_schemas(safe=True)
|
||||
|
||||
await models.Call.filter(ended_at=None).update(ended_at=datetime.now(timezone.utc))
|
||||
await models.Channel.exclude(call_id=None).update(call_id=None)
|
||||
@@ -0,0 +1,82 @@
|
||||
from typing import Tuple
|
||||
|
||||
import discord
|
||||
|
||||
from payphone.db import models
|
||||
|
||||
|
||||
async def get_channel(channel: discord.TextChannel) -> models.Channel:
|
||||
"""Returns a channel from the database, creating or updating it if it already exists.
|
||||
|
||||
Args:
|
||||
channel (discord.TextChannel): The channel to fetch.
|
||||
|
||||
Returns:
|
||||
models.Channel: The channel row.
|
||||
"""
|
||||
|
||||
row, _ = await models.Channel.update_or_create(
|
||||
id=channel.id, server=channel.guild.id
|
||||
)
|
||||
|
||||
channel_name = channel.name.replace("discord", "d").replace("wumpus", "w")
|
||||
webhook_name = f"Payphone #{channel_name}"
|
||||
|
||||
if row.webhook_id is None:
|
||||
webhook = await channel.create_webhook(
|
||||
name=webhook_name, reason="First call started in channel."
|
||||
)
|
||||
|
||||
row.webhook_id = webhook.id
|
||||
row.webhook_url = webhook.url
|
||||
|
||||
await row.save(update_fields=["webhook_id", "webhook_url"])
|
||||
|
||||
else:
|
||||
found = False
|
||||
|
||||
for webhook in await channel.webhooks():
|
||||
if webhook.id != row.webhook_id:
|
||||
if webhook.token is None:
|
||||
await webhook.delete(reason="Webhook is unaccessible by Payphone.")
|
||||
break
|
||||
|
||||
else:
|
||||
found = True
|
||||
|
||||
if not found:
|
||||
webhook = await channel.create_webhook(
|
||||
name=webhook_name, reason="Previous webhook was deleted."
|
||||
)
|
||||
|
||||
row.webhook_id = webhook.id
|
||||
row.webhook_url = webhook.url
|
||||
|
||||
await row.save(update_fields=["webhook_id", "webhook_url"])
|
||||
|
||||
return row
|
||||
|
||||
|
||||
async def get_user(
|
||||
user: discord.User | discord.Member,
|
||||
) -> Tuple[models.User, models.Profile | None]:
|
||||
"""Returns a user from the database, creating or updating it if it already exists.
|
||||
|
||||
Args:
|
||||
user (discord.User | discord.Member): The user or member to fetch.
|
||||
|
||||
Returns:
|
||||
Tuple[models.User, models.Profile]: The user and profile.
|
||||
"""
|
||||
|
||||
user_row, _ = await models.User.update_or_create(id=user.id, username=user.name)
|
||||
|
||||
# TODO: Make per-server profiles a premium feature.
|
||||
profile_row, _ = await models.Profile.update_or_create(
|
||||
user=user_row,
|
||||
server_id=None, # user.guild.id if isinstance(user, discord.Member) else None,
|
||||
avatar_url=user.display_avatar.url,
|
||||
name=user.display_name,
|
||||
)
|
||||
|
||||
return (user_row, profile_row)
|
||||
Reference in New Issue
Block a user