import json import logging import os from datetime import datetime from os.path import basename import pytz import yaml from django.conf import settings from django.contrib.auth.models import User from django.db import models from django.db.models import Q from django.db.models.signals import pre_delete from django.dispatch import receiver logger = logging.getLogger(__name__) class MinecraftManagerUser(User): class Meta: proxy = True permissions = ( ('bots', 'Can use the bot control page'), ('chat', 'Can use chat page'), ) class UserSettings(models.Model): RESULT_OPTIONS = ( (10, '10'), (25, '25'), (50, '50'), (100, '100'), (-1, 'All') ) THEME_OPTIONS = ( ('DE', 'Default'), ('DA', 'Dark'), ('SO', 'Solar'), ('SL', 'Slate') ) tzs = [] for tz in pytz.common_timezones: tzs.append((tz, tz)) TIMEZONES = tuple(tzs) auth_user = models.OneToOneField(User, on_delete=models.CASCADE, null=True, blank=True) default_results = models.SmallIntegerField("Default Results", default=10, choices=RESULT_OPTIONS) default_theme = models.CharField("Theme", max_length=2, default='DE', choices=THEME_OPTIONS) default_timezone = models.CharField("Timezone", max_length=50, default='UTC', choices=TIMEZONES) search_player_ip = models.BooleanField("Include IP in Player search", default=False) show_timestamp_chat = models.BooleanField("Show Timestamp By Chat", default=False) last_ip = models.CharField(max_length=30, default="127.0.0.1", editable=False) class Meta: verbose_name = "User Settings" verbose_name_plural = "User Settings" def __str__(self): return self.auth_user.username def __repr__(self): return self.auth_user.username class Application(models.Model): username = models.CharField("Minecraft Username", max_length=20, unique=True) age = models.PositiveSmallIntegerField() player_type = models.TextField("What's your favorite thing to do in Minecraft?", max_length=300) ever_banned = models.BooleanField("Have you ever been banned?", default=False) ever_banned_explanation = models.TextField("If you were previously banned, will you share why?", max_length=300, blank=True, null=True) reference = models.CharField("How did you find out about us? Please give a website or player name, if applicable.", max_length=50, blank=True, null=True) read_rules = models.CharField("Have you read the rules?", max_length=10) accepted = models.BooleanField(null=True) date = models.DateTimeField(auto_now_add=True, blank=True, null=True) objects = models.Manager() @property def status(self): if self.accepted is not None: if self.accepted is True: return "Accepted" elif self.accepted is False: return "Denied" else: return "Unknown" else: return "Unanswered" @property def date_display(self): return str(self.date).split(".")[0] def __str__(self): return self.username def __repr__(self): return self.username class Player(models.Model): auth_user = models.OneToOneField(User, on_delete=models.SET_NULL, null=True, blank=True) uuid = models.CharField(max_length=36, unique=True) username = models.CharField(max_length=20) application = models.ForeignKey(Application, on_delete=models.SET_NULL, null=True, blank=True) first_seen = models.DateField(null=True, blank=True) last_seen = models.DateField(null=True, blank=True) discord_id = models.CharField(max_length=30, unique=True, null=True, blank=True) @property def is_banned(self): ban_file = os.path.join(settings.MINECRAFT_BASE_DIR, 'banned-players.json') with open(ban_file, encoding='utf-8') as f: bans = json.load(f) if bans: for ban in bans: if self.uuid == ban['uuid']: return True return False else: return False @property def donor_status(self): def format_time(timestamp): return datetime.utcfromtimestamp(timestamp).strftime('%m/%d/%Y') try: from django_luckperms.models import LuckPermsPlayerPermissions as lppp donator = lppp.objects.filter(permission='group.donator', uuid=self.uuid) if donator.exists(): expiry = donator.first().expiry if expiry > 0: return "Until {}".format(format_time(expiry)) else: return "Permanent" else: return "None" except: pass try: pex_file = os.path.join(getattr(settings, 'MINECRAFT_BASE_DIR', ''), 'plugins/PermissionsEx/permissions.yml') with open(pex_file) as f: yml = yaml.load(f) for user in yml['users']: if user == self.uuid: if 'donator' in yml['users'][user]['group']: u = yml['users'][user] if 'group-donator-until' in u['options']: unix_time = float(u['options']['group-donator-until']) return 'Until {0}'.format(format_time(unix_time)) else: return 'Permanent' else: return 'None' except: pass return "N/A" @property def ips(self): ips = [] if not getattr(self.auth_user, 'is_active', False): query = IP.objects.filter(player=self) for q in query: ips.append(q.ip) return " ".join(ips) def __str__(self): return "{} ({})".format(self.username, self.uuid) class Ticket(models.Model): PRIORITY = ( ('L', 'Low'), ('M', 'Medium'), ('H', 'High') ) WORLDS = ( ('O', 'Overworld'), ('N', 'Nether'), ('E', 'The End') ) player = models.ForeignKey(Player, related_name="ticket_player", on_delete=models.CASCADE, blank=True, null=True) message = models.CharField(max_length=500) priority = models.CharField(max_length=1, choices=PRIORITY, blank=True, default='L') staff = models.ForeignKey(User, related_name="ticket_staff", on_delete=models.CASCADE, null=True, blank=True) resolved = models.BooleanField(default=False) world = models.CharField(max_length=1, choices=WORLDS, blank=True, null=True) x = models.CharField(max_length=20, blank=True, null=True) y = models.CharField(max_length=20, blank=True, null=True) z = models.CharField(max_length=20, blank=True, null=True) date = models.DateTimeField(auto_now_add=True, null=True, blank=True) objects = models.Manager() @property def location(self): return "{0}, {1}, {2} in {3}".format(self.x, self.y, self.z, self.world_label) @property def world_label(self): for W in self.WORLDS: if W[0] == self.world: return W[1] return "" @property def resolved_label(self): if self.resolved: return "Resolved" else: return "Unresolved" @property def snippet(self): if len(self.message) > 50: return self.message[:50] + "..." else: return self.message @property def issuer(self): if self.player: pl = Player.objects.get(id=self.player_id) return pl.username else: return "Unknown" @property def claimed_by(self): if self.staff: pl = User.objects.get(id=self.staff_id) return pl.username else: return "Unclaimed" @property def is_claimed(self): if self.staff: return True else: return False @property def priority_display(self): for P in self.PRIORITY: if P[0] == self.priority: return P[1] return "Unknown" @staticmethod def priority_code_to_display(piority_code): for P in Ticket.PRIORITY: if P[0] == piority_code: return P[1] return "Unknown" @property def date_display(self): return str(self.date).split(".")[0] @property def notes(self): return TicketNote.objects.filter(ticket=self) def __str__(self): return "{}: {}".format(self.issuer, self.snippet) class TicketNote(models.Model): author = models.ForeignKey(User, on_delete=models.CASCADE) ticket = models.ForeignKey(Ticket, on_delete=models.CASCADE) message = models.TextField(max_length=1000) last_update = models.DateTimeField(auto_now_add=True, blank=True, null=True) date = models.DateTimeField(auto_now_add=True, blank=True, null=True) @property def attachments(self): return Attachment.objects.filter(ref_model=RefModels.TICKET_NOTE[0], ref_id=self.id) class Meta: verbose_name = "Ticket Note" verbose_name_plural = "Ticket Notes" def __str__(self): return "Ticket: {0}".format(self.ticket.snippet) class Note(models.Model): IMPORTANCE = ( ('L', 'Low'), ('M', 'Medium'), ('H', 'High') ) player = models.ForeignKey(Player, related_name="note_player", on_delete=models.CASCADE) message = models.CharField(max_length=200) importance = models.CharField(max_length=1, choices=IMPORTANCE) staff = models.ForeignKey(User, related_name="note_staff", on_delete=models.CASCADE, null=True, blank=True, limit_choices_to=Q(is_active=True)) date = models.DateTimeField(auto_now_add=True, blank=True, null=True) @property def snippet(self): if len(self.message) > 50: return self.message[:50] + "..." else: return self.message @property def importance_display(self): for S in self.IMPORTANCE: if S[0] == self.importance: return S[1] return "Unknown" @staticmethod def importance_code_to_display(importance_code): for S in Note.IMPORTANCE: if S[0] == importance_code: return S[1] return "Unknown" @property def issuer(self): if self.staff: pl = User.objects.get(id=self.staff_id) return pl.username else: return "System" @property def issuee(self): if self.player: pl = Player.objects.get(id=self.player_id) return pl.username else: return "Unknown" @property def date_display(self): return str(self.date).split(".")[0] @property def attachments(self): return Attachment.objects.filter(ref_model=RefModels.NOTE[0], ref_id=self.id) def __str__(self): return "{}: {}".format(self.issuee, self.snippet) class RefModels: TICKET_NOTE = 'T', 'Ticket Note' NOTE = 'N', 'Note' choices = TICKET_NOTE, NOTE @staticmethod def label(ref: str): for c in RefModels.choices: if c[0] == ref: return c[1] return "None" def _attachment_upload_to(instance, filename): return f"attachments/{instance.ref_name.lower().replace(' ', '_')}s/{instance.ref_id}/{filename}" class Attachment(models.Model): ref_model = models.CharField(max_length=1, choices=RefModels.choices) ref_id = models.IntegerField() file = models.FileField(upload_to=_attachment_upload_to) created = models.DateTimeField(auto_now_add=True) @property def ref_name(self): return RefModels.label(self.ref_model) @property def file_name(self): return basename(self.file.name) def __str__(self): return self.file.name @receiver(pre_delete, sender=Attachment, dispatch_uid="delete_attachments") def attachment_delete(sender, instance, **kwargs): instance.file.delete(False) class IPManager(models.Manager): def get_queryset(self): users = User.objects.filter(is_active=True) filtered = [] for user in users: if getattr(user, 'player', False): ips = IP.objects.filter(player=user.player) for ip in ips: filtered.append(ip.ip) return super(IPManager, self).get_queryset().exclude(ip__in=filtered) class IP(models.Model): player = models.ForeignKey(Player, on_delete=models.CASCADE) ip = models.CharField(max_length=30) last_used = models.DateField(null=True, blank=True) objects = models.Manager() api = IPManager() class Meta: verbose_name = "IP" verbose_name_plural = "IPs" @property def last_used_formatted(self): if self.last_used: return self.last_used else: return "N/A" @property def associated(self): ips = IP.api.filter(ip=self.ip) players = [] for ip in ips: if self.player != ip.player: players.append(ip.player) if players: return players else: return None def __str__(self): player = Player.objects.get(id=self.player_id) return "{}: {}".format(player.username, self.ip) class Alert(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) message = models.TextField(max_length=1000) seen = models.BooleanField(default=False) date = models.DateTimeField(auto_now_add=True, blank=True, null=True) @property def snippet(self): if len(self.message) > 50: return self.message[:50] + "..." else: return self.message def __str__(self): return "Alert for {}".format(self.user.username)