Files
payphone/payphone/db/models.py
T

352 lines
11 KiB
Python

from datetime import datetime
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, db_index=True)
is_masked: bool = fields.BooleanField(default=False)
is_moderator: bool = fields.BooleanField(default=False)
profiles: fields.BackwardFKRelation["Profile"]
call_rss: fields.BackwardFKRelation["CallUserChannelRelationship"]
leaderboard_position: int | None = fields.IntField(
default=None, null=True, db_index=True
)
leaderboard_calls_count: int | None = fields.IntField(default=None, null=True)
joined_at: datetime = fields.DatetimeField(auto_now_add=True)
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"]
messages: fields.BackwardFKRelation["Message"]
class Profile(models.Model):
user: fields.ForeignKeyRelation["User"] = fields.ForeignKeyField(
"models.User", related_name="profiles", on_delete=fields.OnDelete.CASCADE
)
user_id: int
server: fields.ForeignKeyNullableRelation["Server"] = fields.ForeignKeyField(
"models.Server",
related_name="profiles",
null=True,
on_delete=fields.OnDelete.CASCADE,
)
avatar_url: str = fields.TextField()
name: str = fields.CharField(max_length=32)
color: int | None = fields.IntField(null=True, default=None)
call_rss: fields.BackwardFKRelation["CallUserChannelRelationship"]
@property
def jump_url(self) -> str:
"""The URL that'll open the user in Discord.
Returns:
str: The URL.
"""
return f"discord://-/users/{self.user_id}"
@property
def link(self) -> str:
"""A markdown link that opens the user in Discord.
Returns:
str: The markdown link.
"""
return f"[{self.name}]({self.jump_url})"
class Meta:
unique_together = [("user_id", "server_id")]
class Server(models.Model):
id: int = fields.BigIntField(primary_key=True)
prefix: str = fields.CharField(max_length=5, default="p.")
channels: fields.BackwardFKRelation["Channel"]
profiles: fields.BackwardFKRelation["Profile"]
class Channel(models.Model):
id = fields.BigIntField(primary_key=True)
server: fields.ForeignKeyRelation[Server] = fields.ForeignKeyField(
"models.Server", related_name="channels", on_delete=fields.OnDelete.CASCADE
)
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.
Args:
session (aiohttp.ClientSession): An initialized aiohttp client session.
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.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()
created_at: datetime = fields.DatetimeField(auto_now_add=True, db_index=True)
@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")]
class Message(models.Model):
id = fields.BigIntField(primary_key=True)
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)
user_id: int
profile: fields.ForeignKeyRelation["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)
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}>"
def jump_url(self, is_this_channel: bool = True) -> str | None:
"""Returns the jump URL for this message.
Args:
is_this_channel (bool, optional): Whether the message sent in this channel or not.
Defaults to True.
Returns:
str | None: The URL, or None if the message was not propagated.
"""
if not is_this_channel:
return f"https://discord.com/channels/{self.channel.server_id}/{self.channel.id}/{self.id}"
if self.propagation_id is not None:
return f"https://discord.com/channels/{self.propagation_channel.server_id}/{self.propagation_channel.id}/{self.propagation_id}"
return None
class MessageContent(models.Model):
message = fields.ForeignKeyField(
"models.Message", related_name="contents", on_delete=fields.OnDelete.CASCADE
)
content = fields.CharField(max_length=4096)
was_propagated = fields.BooleanField(default=True)
created_at = fields.DatetimeField(auto_now_add=True)
class ReportStatus(enum.IntEnum):
ACCEPTED = 0
DECLINED = 1
ON_REVIEW = 2
class Report(models.Model):
call: fields.OneToOneRelation["Call"] = fields.OneToOneField(
"models.Call", related_name="report"
)
reported_by: fields.ForeignKeyNullableRelation["User"] = fields.ForeignKeyField(
"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, default=ReportStatus.ON_REVIEW, db_index=True
)
created_at: datetime = fields.DatetimeField(auto_now_add=True)
class BanStatus(enum.IntEnum):
VALID = 0
ON_REVIEW = 1
APPEAL_ACCEPTED = 2
APPEAL_DECLINED = 3
class Ban(models.Model):
user_id: int = fields.BigIntField()
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, default=BanStatus.VALID)
banned_by: fields.ForeignKeyRelation["User"] = fields.ForeignKeyField(
"models.User", related_name="bans_created", null=True
)
created_at: datetime = fields.DatetimeField(auto_now_add=True, db_index=True)
expires_at: datetime | None = fields.DatetimeField(null=True)
class Meta:
indexes = [("user_id", "expires_at")]