385 lines
11 KiB
Python
385 lines
11 KiB
Python
# -*- encoding: utf-8 -*-
|
|
|
|
import logging
|
|
from pathlib import Path
|
|
|
|
from django.db import models
|
|
from django.utils import timezone
|
|
from django.utils.translation import gettext_lazy as _
|
|
from django.core.validators import MaxValueValidator, MinValueValidator
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
class GuildSettings(models.Model):
|
|
"""
|
|
Represents settings for a saved Discord Guild `SavedGuild`.
|
|
These objects aren't linked through foreignkey because
|
|
`SavedGuild` is user user unique, not Discord Guild unique.
|
|
"""
|
|
|
|
id = models.AutoField(primary_key=True)
|
|
|
|
guild_id = models.CharField(
|
|
verbose_name=_("guild id"),
|
|
max_length=128,
|
|
help_text=_("Discord snowflake ID for the represented guild."),
|
|
unique=True
|
|
)
|
|
|
|
default_embed_colour = models.CharField(
|
|
verbose_name=_("default embed colour"),
|
|
max_length=6,
|
|
default="3498db",
|
|
blank=True
|
|
)
|
|
|
|
active = models.BooleanField(
|
|
verbose_name=_("Active"),
|
|
default=True,
|
|
help_text=_("Subscriptions of inactive guilds will also be treated as inactive")
|
|
)
|
|
|
|
class Meta:
|
|
"""
|
|
Metadata for the GuildSettings model.
|
|
"""
|
|
|
|
verbose_name = "guild settings"
|
|
verbose_name_plural = "guild settings"
|
|
get_latest_by = "id"
|
|
|
|
|
|
class SavedGuilds(models.Model):
|
|
"""
|
|
Represents a saved Discord Guild (aka Server).
|
|
These are shown in the UI on the sidebar, and can be selected
|
|
to see associated Subscriptions.
|
|
"""
|
|
|
|
id = models.AutoField(_("ID"), primary_key=True)
|
|
|
|
# Have to use charfield instead of positiveBigIntegerField due to an Sqlite
|
|
# issue that rounds down the value
|
|
# https://github.com/sequelize/sequelize/issues/9335
|
|
guild_id = models.CharField(
|
|
verbose_name=_("guild id"),
|
|
max_length=128,
|
|
help_text=_("Discord snowflake ID for the represented guild.")
|
|
)
|
|
|
|
name = models.CharField(
|
|
max_length=128,
|
|
help_text=_("Name of the represented guild.")
|
|
)
|
|
|
|
icon = models.CharField(
|
|
max_length=128,
|
|
help_text=_("Hash for the represented guild's icon.")
|
|
)
|
|
|
|
added_by = models.ForeignKey(
|
|
verbose_name=_("added by"),
|
|
to="authentication.DiscordUser",
|
|
on_delete=models.CASCADE,
|
|
help_text=_("The user who added created this instance.")
|
|
)
|
|
|
|
permissions = models.CharField(
|
|
max_length=64,
|
|
help_text=_("Guild permissions for the user who added this instance.")
|
|
)
|
|
|
|
owner = models.BooleanField(
|
|
default=False,
|
|
help_text=_("Does the 'added by' user own this guild?")
|
|
)
|
|
|
|
class Meta:
|
|
"""
|
|
Metadata for the SavedGuilds Model.
|
|
"""
|
|
|
|
verbose_name = "saved guild"
|
|
verbose_name_plural = "saved guilds"
|
|
get_latest_by = "-creation_datetime"
|
|
|
|
constraints = [
|
|
# Prevent servers from having subscriptions with duplicate names
|
|
models.UniqueConstraint(
|
|
fields=["added_by", "guild_id"],
|
|
name="unique added_by & guild_id pair"
|
|
)
|
|
]
|
|
|
|
def __str__(self) -> str:
|
|
return self.name
|
|
|
|
@property
|
|
def settings(self):
|
|
return GuildSettings.objects.get(guild_id=self.guild_id)
|
|
|
|
def save(self, *args, **kwargs):
|
|
GuildSettings.objects.get_or_create(guild_id=self.guild_id)
|
|
super().save(*args, **kwargs)
|
|
|
|
|
|
class SubChannel(models.Model):
|
|
"""
|
|
Represents a Discord TextChannel, saved against a Subscription.
|
|
SubChannels are used as targets to send content from Subscriptions.
|
|
"""
|
|
|
|
id = models.AutoField(primary_key=True)
|
|
|
|
# Have to use charfield instead of positiveBigIntegerField due to an Sqlite
|
|
# issue that rounds down the value
|
|
# https://github.com/sequelize/sequelize/issues/9335
|
|
channel_id = models.CharField(
|
|
verbose_name=_("channel id"),
|
|
max_length=128,
|
|
help_text=_("Discord snowflake ID for the represented Channel.")
|
|
)
|
|
|
|
channel_name = models.CharField(
|
|
verbose_name=_("channel name"),
|
|
max_length=256,
|
|
help_text=_("Name of the represented Channel.")
|
|
)
|
|
|
|
subscription = models.ForeignKey(
|
|
to="home.Subscription",
|
|
on_delete=models.CASCADE,
|
|
help_text=_("The linked Subscription, must be unique.")
|
|
)
|
|
|
|
class Meta:
|
|
"""
|
|
Metadata for the SubChannel Model.
|
|
"""
|
|
|
|
verbose_name = "SubChannel"
|
|
verbose_name_plural = "SubChannels"
|
|
get_latest_by = "id"
|
|
constraints = [
|
|
# Prevent servers from having subscriptions with duplicate names
|
|
models.UniqueConstraint(
|
|
fields=["channel_id", "subscription"],
|
|
name="unique channel id and subscription pair")
|
|
]
|
|
|
|
def __str__(self) -> str:
|
|
return self.channel_id
|
|
|
|
|
|
# using a brilliant matching model design from paperless-ngx src
|
|
class Filter(models.Model):
|
|
MATCH_NONE = 0
|
|
MATCH_ANY = 1
|
|
MATCH_ALL = 2
|
|
MATCH_LITERAL = 3
|
|
MATCH_REGEX = 4
|
|
MATCH_FUZZY = 5
|
|
MATCH_AUTO = 6
|
|
|
|
MATCHING_ALGORITHMS = (
|
|
(MATCH_NONE, _("None")),
|
|
(MATCH_ANY, _("Any: Item contains any of these words (space separated)")),
|
|
(MATCH_ALL, _("All: Item contains all of these words (space separated)")),
|
|
(MATCH_LITERAL, _("Exact: Item contains this string")),
|
|
(MATCH_REGEX, _("Regular expression: Item matches this regex")),
|
|
(MATCH_FUZZY, _("Fuzzy: Item contains a word similar to this word")),
|
|
# (MATCH_AUTO, _("Automatic")),
|
|
)
|
|
|
|
id = models.AutoField(primary_key=True)
|
|
|
|
name = models.CharField(_("name"), max_length=128)
|
|
|
|
match = models.CharField(_("match"), max_length=256, blank=True)
|
|
|
|
matching_algorithm = models.PositiveIntegerField(
|
|
_("matching algorithm"),
|
|
choices=MATCHING_ALGORITHMS,
|
|
default=MATCH_ANY,
|
|
)
|
|
|
|
is_insensitive = models.BooleanField(_("is insensitive"), default=True)
|
|
|
|
is_whitelist = models.BooleanField(_("is whitelist"), default=False)
|
|
|
|
# Have to use charfield instead of positiveBigIntegerField due to an Sqlite
|
|
# issue that rounds down the value
|
|
# https://github.com/sequelize/sequelize/issues/9335
|
|
guild_id = models.CharField(_("guild id"), max_length=128)
|
|
|
|
class Meta:
|
|
ordering = ("name",)
|
|
constraints = [
|
|
models.UniqueConstraint(
|
|
fields=["name", "guild_id"],
|
|
name="unique name & guild id pair"
|
|
)
|
|
]
|
|
|
|
def __str__(self):
|
|
return f"{self.guild_id} - {self.name}"
|
|
|
|
|
|
class Subscription(models.Model):
|
|
"""
|
|
The Subscription Model.
|
|
'Subscription' in the context of PYRSS is an RSS Feed with various settings.
|
|
"""
|
|
|
|
id = models.AutoField(primary_key=True)
|
|
|
|
name = models.CharField(
|
|
_("Name"),
|
|
max_length=32,
|
|
null=False,
|
|
blank=False
|
|
)
|
|
|
|
url = models.URLField(_("URL"))
|
|
|
|
# NOTE:
|
|
# Have to use charfield instead of positiveBigIntegerField due to an Sqlite
|
|
# issue that rounds down the value
|
|
# https://github.com/sequelize/sequelize/issues/9335
|
|
guild_id = models.CharField(
|
|
_("Guild ID"),
|
|
max_length=128
|
|
)
|
|
|
|
creation_datetime = models.DateTimeField(
|
|
_("Created At"),
|
|
default=timezone.now,
|
|
editable=False
|
|
)
|
|
|
|
extra_notes = models.CharField(
|
|
_("Extra Notes"),
|
|
max_length=250,
|
|
null=True,
|
|
blank=True,
|
|
)
|
|
|
|
filters = models.ManyToManyField(to="home.Filter", blank=True)
|
|
|
|
article_title_mutators = models.ManyToManyField(
|
|
to="home.ArticleMutator",
|
|
related_name="title_mutated_subscriptions",
|
|
blank=True,
|
|
)
|
|
|
|
article_desc_mutators = models.ManyToManyField(
|
|
to="home.ArticleMutator",
|
|
related_name="desc_mutated_subscriptions",
|
|
blank=True,
|
|
)
|
|
|
|
embed_colour = models.CharField(
|
|
_("Embed Colour"),
|
|
max_length=6,
|
|
default="3498db",
|
|
blank=True
|
|
)
|
|
|
|
published_threshold = models.DateTimeField(_("Published Threshold"), default=timezone.now, blank=True)
|
|
|
|
article_fetch_image = models.BooleanField(
|
|
_("Fetch Article Images"),
|
|
default=True,
|
|
help_text="Will the resulting article have an image?"
|
|
)
|
|
|
|
active = models.BooleanField(_("Active"), default=True)
|
|
|
|
class Meta:
|
|
"""
|
|
Metadata for the Subscription Model.
|
|
"""
|
|
|
|
verbose_name = "subscription"
|
|
verbose_name_plural = "subscriptions"
|
|
get_latest_by = "-creation_datetime"
|
|
constraints = [
|
|
# Prevent servers from having subscriptions with duplicate names
|
|
models.UniqueConstraint(fields=["name", "guild_id"], name="unique name & server pair")
|
|
]
|
|
|
|
@property
|
|
def channels_count(self) -> int:
|
|
"""
|
|
Returns the number of 'SubChannel' objects assocaited
|
|
with this subscription.
|
|
"""
|
|
|
|
return len(SubChannel.objects.filter(subscription=self))
|
|
|
|
def save(self, *args, **kwargs):
|
|
new_text = "New " if self._state.adding else ""
|
|
log.debug("%sSubscription Saved %s", new_text, self.id)
|
|
super().save(*args, **kwargs)
|
|
|
|
def __str__(self) -> str:
|
|
return self.name
|
|
|
|
|
|
class TrackedContent(models.Model):
|
|
"""
|
|
Tracked Content Model
|
|
'Tracked Content' identifies articles and tracks them being sent.
|
|
This is used to ensure duplicate articles aren't sent in feeds.
|
|
"""
|
|
|
|
id = models.AutoField(_("ID"), primary_key=True)
|
|
|
|
guid = models.CharField(
|
|
_("GUID"),
|
|
max_length=256,
|
|
help_text=_("RSS provided GUID of the content")
|
|
)
|
|
|
|
title = models.CharField(_("Title"), max_length=728)
|
|
|
|
url = models.URLField(_("URL"))
|
|
|
|
subscription = models.ForeignKey(to=Subscription, on_delete=models.CASCADE)
|
|
|
|
channel_id = models.CharField(_("Channel ID"), max_length=128)
|
|
|
|
message_id = models.CharField(_("Message ID"), max_length=128)
|
|
|
|
blocked = models.BooleanField(_("Blocked"), default=False)
|
|
|
|
creation_datetime = models.DateTimeField(
|
|
_("Created At"),
|
|
default=timezone.now,
|
|
editable=False
|
|
)
|
|
|
|
class Meta:
|
|
|
|
verbose_name = "tracked content"
|
|
verbose_name = "tracked contents"
|
|
get_latest_by = "-creation_datetime"
|
|
constraints = [
|
|
models.UniqueConstraint(fields=["guid", "channel_id"], name="unique guid & channel_id pair"),
|
|
models.UniqueConstraint(fields=["url", "channel_id"], name="unique url & channel_id pair")
|
|
]
|
|
|
|
def __str__(self) -> str:
|
|
return self.title
|
|
|
|
|
|
class ArticleMutator(models.Model):
|
|
|
|
id = models.AutoField(primary_key=True)
|
|
name = models.CharField(max_length=64)
|
|
value = models.CharField(max_length=32)
|
|
|
|
def __str__(self) -> str:
|
|
return self.name
|