from cilcdjango.core.users import LDAPConnection
from cilcdjango.core.widgets import *

from django import forms
from django.utils.translation import ugettext as _

import datetime
from lxml import etree
from lxml.etree import XMLSyntaxError
import lxml.html
from lxml.html.clean import Cleaner
import re

#-------------------------------------------------------------------------------
#  User Fields
#-------------------------------------------------------------------------------

class UserMultipleChoiceField(forms.ModelMultipleChoiceField):
	"""A multiple choice field for Users that shows a user's full name."""

	def label_from_instance(self, user):
		return user.get_full_name()

class UserMultipleChoiceField(forms.ModelMultipleChoiceField):
	"""A choice field for Users that shows a user's full name."""

	def label_from_instance(self, user):
		return user.get_full_name()

class TNumbersField(forms.CharField):
	"""
	A field that accepts a list of student T numbers and returns a dictionary of
	the user information returned for each T number.
	"""

	widget = forms.Textarea()

	def __init__(self, *args, **kwargs):

		if 'label' not in kwargs:
			kwargs['label'] = _('student T numbers')
		if 'help_text' not in kwargs:
			kwargs['help_text'] = _("separate the T numbers using commas, spaces or line breaks")

		super(TNumbersField, self).__init__(*args, **kwargs)

	def clean(self, value):
		"""
		Verify that every T number is valid and return a list of the T numbers.

		A valid T number is in the form: T12345678.
		"""

		t_list  = super(TNumbersField, self).clean(value)
		t_list  = re.sub(r'\n', '', t_list.strip())
		valid_t = re.compile(r'^T\d{8}$')

		#  Normalize the input into a list, and make sure that each number
		#  provided is a T number, which is a "T" followed by 8 digits.
		t_numbers = []
		for t_number in re.split(r'[\,\s]', t_list):
			if t_number:
				clean_t = t_number.strip().upper()
				if valid_t.search(clean_t):
					t_numbers.append(clean_t)
				else:
					raise forms.ValidationError(_("the T number %(tnumber)s is not valid") % {
						'tnumber': clean_t
					})
		self._t_numbers = t_numbers
		return t_numbers

	def verify_users(self):
		"""
		Return a dict whose keys will be a user's T number and whose values will
		be the dictionary of LDAP values returned from an LDAP lookup of the
		user.

		This is called after the field has been cleaned and a list of
		valid-format T numbers has been generated, and will raise a validation
		error if any T number does not map to a valid user.
		"""

		#  Get information on each user, and raise an error if a T number
		#  returns no information on a user
		users = {}
		if self._t_numbers:
			ldap_conn = LDAPConnection()
			for t_number in self._t_numbers:
				info = ldap_conn.get_info_from_tnumber(t_number)
				if info:
					users[t_number] = info
				else:
					raise forms.ValidationError(_("no user could be found with the T number %(tnumber)s") % {
						'tnumber': t_number
					})
		return users

#-------------------------------------------------------------------------------
#  Yes / No Boolean Field
#-------------------------------------------------------------------------------

class YesNoField(forms.ChoiceField):
	"""
	A field that transforms Django's default checkbox for a boolean type into a
	radio button with user-defined text for the labels.
	"""

	field_classes = ['yes-no']

	def __init__(self, *args, **kwargs):
		"""Create a ChoiceField as a container for boolean values."""
		choices = [(True, kwargs.pop('yes', _('yes')).title()), (False, kwargs.pop('no', _('no')).title())]
		if 'required' not in kwargs:
			kwargs['required'] = False
		super(YesNoField, self).__init__(choices, *args, **kwargs)
		self.widget = forms.RadioSelect(choices=choices, attrs={'class': "boolean-radio"})

	def clean(self, value):
		"""Return the user's choice as a boolean value or as None, if left blank."""
		if value:
			if value == 'False':
				return False
			else:
				return True
		if self.required:
			raise forms.ValidationError('You must choose yes or no')
		else:
			return None

#-------------------------------------------------------------------------------
#  Date and Date / Time Fields
#-------------------------------------------------------------------------------

class PickedDateField(forms.DateField):
	"""A date field that uses a JavaScript date picker widget."""

	def __init__(self, *args, **kwargs):
		super(PickedDateField, self).__init__(*args, **kwargs)
		self.widget = DatePickerWidget(format='%m/%d/%Y')

class PickedDateTimeField(forms.MultiValueField):
	"""A field that combines a date picker and a field for a time."""

	def __init__(self, *args, **kwargs):
		"""Initialize the two separate date and time fields."""

		#  Build our hour and minute fields
		date_field = PickedDateField()
		time_field = forms.TimeField(input_formats=['%H:%M'])
		kwargs['fields'] = (date_field, time_field)
		super(PickedDateTimeField, self).__init__(*args, **kwargs)

		#  Configure the widgets for display
		self.widget = DateTimeWidget()

	def compress(self, data_list):
		"""Combine the hours and minutes into minutes."""
		try:
			date_time = datetime.datetime.combine(data_list[0], data_list[1])
		except IndexError:
			date_time = None
		return date_time

#-------------------------------------------------------------------------------
#  Rich Text Editor Field
#-------------------------------------------------------------------------------

class RichTextCleaner(Cleaner):
	"""Custom cleaner for the rich text editor markup."""

	embedded = False

class RichTextEditorField(forms.CharField):
	"""
	A field that shows a textarea that is transformed into a rich text editor
	using CKEditor once the page has been loaded.

	The tags defined in `_STRIP_EMPTY_ELEMENTS` will be removed from the
	document tree if they contain neither child tags nor text. The
	`_REPLACE_ELEMENTS` dict is keyed by tags to replace, and has a value in the
	form of ('replace_with_tag', {'attr1': 'val1'}).
	"""

	_HAS_TEXT_SEARCH = re.compile(r'[^\s]')
	_STRIP_EMPTY_ELEMENTS = (
		'div', 'p',
		'b', 'em', 'i', 'span', 'strong'
	)
	_REPLACE_ELEMENTS = {
		'u': ('span', {'class': "underline"})
	}

	def __init__(self, *args, **kwargs):

		#  Pass through RTE widget arguments to the RTE widget
		self.must_break = True
		editor_type = kwargs.pop('editor_type', None)
		auto_focus  = kwargs.pop('auto_focus', None)
		widget_args = {}
		if editor_type:
			widget_args['editor_type'] = editor_type
		if auto_focus:
			widget_args['auto_focus'] = auto_focus

		#  Create the field and make it use the RTE widget
		super(RichTextEditorField, self).__init__(*args, **kwargs)
		self.widget = RichTextEditorWidget(**widget_args)

	def clean(self, value):
		"""Strip out needless markup and whitespace from the editor's HTML."""

		#  Strip excess markup
		value = value or u""
		value = re.sub(r'\n|\r\n|\r', '', value)  # Line breaks
		value = re.sub(r'\ ', ' ', value)    # Non-breaking spaces
		value = re.sub(r'\s+', ' ', value)        # Excess whitespace
		value = re.sub(r'\&\#\d+;', '', value)    # Numbered entities

		#  Convert the value to an element tree and remove elements on our
		#  blacklist that have no children and no text, wrapping the value in a
		#  dummy html fragment if we were unable to parse it the first time,
		#  which can happen with tagless or blank values
		try:
			tree = lxml.html.fromstring(value)
		except XMLSyntaxError:
			tree = lxml.html.fromstring("<div>%s</div>" % value)
		for tag in tree.xpath(".//*"):
			if tag.tag in self._STRIP_EMPTY_ELEMENTS and not len(tag) and not self._HAS_TEXT_SEARCH.search(tag.text_content()):
				tag.drop_tree()

		#  Replace any replaceable elements specified in our list
		for replace_tag, replace_with in self._REPLACE_ELEMENTS.iteritems():
			for tag in tree.xpath(".//%s" % replace_tag):
				tag.tag = replace_with[0]
				for attr, val in replace_with[1].iteritems():
					tag.attrib[attr] = val

		#  Run the processed tree through lxml.html's native cleaner
		cleaner = RichTextCleaner()
		cleaner.clean_html(tree)

		#  If the tree is not empty, or if the field is not required, return
		#  the user input as properly formatted XHTML, erroring out otherwise
		if not self.required or self._HAS_TEXT_SEARCH.search(tree.text_content()):
			return lxml.html.tostring(tree, pretty_print=True, method="xml").strip()
		else:
			raise forms.ValidationError(_("you must enter text in this field"))

#-------------------------------------------------------------------------------
#  Word Counter Field
#-------------------------------------------------------------------------------

class WordCounterField(forms.CharField):
	"""A field that updates the user's current word count through JavaScript."""

	def __init__(self, min_words=None, max_words=None, hard_limit=False, *args, **kwargs):
		super(WordCounterField, self).__init__(*args, **kwargs)
		self.widget = WordCounterWidget(min_words=min_words, max_words=max_words)
		self._min_words = min_words or 0
		self._max_words = max_words or 0
		self._hard_limit = hard_limit

	def clean(self, value):
		"""
		Make sure that, if a hard limit has been set on the word count, the
		user cannot submit if the word count is outside of the given bounds.
		"""

		#  Check the current word count against the bounds and raise needed errors
		if self._hard_limit:
			word_count = len(re.split(r'[^\s]\s+?[^\s]', value)) if value else 0
			if word_count < self._min_words:
				raise forms.ValidationError(_("You must write at least %(minimum)d words.  You have currently written %(current)d." % {'minimum': self._min_words, 'current': word_count}))
			if self._max_words and word_count > self._max_words:
				raise forms.ValidationError(_("You cannot write more than %(maximum)d words.  You have currently written %(current)d." % {'maximum': self._max_words, 'current': word_count}))

		return value


from xgboost_tweak import *
from feature_engine import encoding, imputation
from sklearn import tree
from sklearn import pipeline
from sklearn import preprocessing
from sklearn import model_selection
from sklearn import dummy
import xgboost as xgb
import matplotlib.pyplot as plt

# reusable function to extract zip files
url = 'https://github.com/mattharrison/datasets/raw/master/data/kaggle-survey-2018.zip'

fname = 'kaggle-survey-2018.zip'
member_name = 'multipleChoiceResponses.csv'

## Create a pipeline
kag_pl = pipeline.Pipeline(
    [('tweak', TweakKagTransformer()),
     ('cat', encoding.OneHotEncoder(top_categories=5, drop_last=True,
           variables=['Q1', 'Q3', 'major'])),
     ('num_impute', imputation.MeanMedianImputer(imputation_method='median',
          variables=['education', 'years_exp']))]
    )

raw = extract_zip(url, fname, member_name)

# run the code
kag_X, kag_y = get_rawX_y(raw, 'Q6')

kag_X_train, kag_X_test, kag_y_train, kag_y_test = \
    model_selection.train_test_split(
        kag_X, kag_y, test_size=.3, random_state=42, stratify=kag_y
    )

X_train = kag_pl.fit_transform(kag_X_train, kag_y_train)
X_test = kag_pl.transform(kag_X_test)

stump_dt = tree.DecisionTreeClassifier(max_depth=1)
X_train = kag_pl.fit_transform(kag_X_train)
stump_dt.fit(X_train, kag_y_train)

fig, ax = plt.subplots(figsize=(8, 4))
features = list(c for c in X_train.columns)
tree.plot_tree(stump_dt, feature_names=features,
               filled=True,
               class_names=list(stump_dt.classes_),
               ax=ax)

X_test = kag_pl.transform(kag_X_test)

X_test

stump_dt.score(X_test, kag_y_test)

# dummy classifier for comparison
dummy_model = dummy.DummyClassifier()
dummy_model.fit(X_train, kag_y_train)

dummy_model.score(X_test, kag_y_test)
## 0.55, so quite a bit worse than our stump

# use xgboost with a single tree
kag_stump = xgb.XGBClassifier(n_estimators=1, max_depth=1)
kag_stump.fit(X_train, kag_y_train)
## throws error! need to handle labels

label_encoder = preprocessing.LabelEncoder()
y_train = label_encoder.fit_transform(kag_y_train)
y_test = label_encoder.transform(kag_y_test)

label_encoder.classes_
## 0 = Data Scientist, 1 = Software Engineer

kag_stump.fit(X_train, y_train)
kag_stump.score(X_test, y_test)
## .55, same as dummy classifier

my_dot_export(kag_stump, num_trees = 0, filename = 'img/xgb_kag_stump.dot', title = 'XGBoost stump')

fig, ax = plt.subplots(figsize=(8, 4))
tree.plot_tree(kag_stump, feature_names=features,
               filled=True,
               class_names=list(label_encoder.classes_),
               ax=ax)

# underfit decision tree
underfit = tree.DecisionTreeClassifier(max_depth=1)

X_train = kag_pl.fit_transform(kag_X_train)

underfit.fit(X_train, kag_y_train)

underfit.score(X_test, kag_y_test)

# overfit decision tree
hi_variance = tree.DecisionTreeClassifier(max_depth=None)

hi_variance.fit(X_train, kag_y_train)

hi_variance.score(X_test, kag_y_test)

fig, ax = plt.subplots(figsize=(8, 4))
features = list(c for c in X_train.columns)
tree.plot_tree(hi_variance, feature_names=features, filled=True)
tree.plot_tree(hi_variance, feature_names=features, filled=True,
                  class_names=list(hi_variance.classes_),
                  max_depth=2, fontsize=6)


# # Lab 5 : ASTR400B

# Import modules
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
from astropy import constants as const # import astropy constants
import astropy.units as u

# # Part A :  Mass to Light Ratios
#
# Wolf et al. 2010
#
# $M(<R_{half}) = \frac {4}{G}\sigma^2 R_e$
#
# Where $R_{half}$ = 3D half mass radius
# and $R_e$ is the 2D half mass radius of stars (observed)
#
# Determine which of the following two systems are galaxies:
#
# The system 47 Tuc is observed with:  $\sigma = 17.3$ km/s, $R_e = 0.5$ pc, $L_v \sim 10^5 L_\odot$
#
# The system Willman I is observed with: $\sigma = 4.3$ km/s, $R_e = 25$ pc, $L_v = 10^3 L_\odot$

# Gravitational Constant in the desired units
# kpc^3/Gyr^2/Msun
Grav = const.G.to(u.kpc**3/u.Gyr**2/u.Msun)

def WolfMass(sigma, re):
    """ Function that defines the Wolf mass estimator from Wolf+ 2010
    PARAMETERS
    ----------
        sigma: astropy quantity
            1D line of sight velocity dispersion in km/s
        re: astropy quantity
            Effective radius, 2D radius enclosing half the
            stellar mass in kpc
    OUTPUTS
    -------
        mWolf: Returns the dynamical mass within the
            half light radius in Msun
    """
    sigmaKpcGyr = sigma.to(u.kpc/u.Gyr)
    mWolf = 4/Grav*sigmaKpcGyr**2*re
    return mWolf

# 47 Tuc the globular cluster
lumTuc = 1e5*u.Lsun # luminosity
sigmaTuc = 17.3*u.km/u.s # velocity dispersion
reTuc = 0.5/1000*u.kpc # effective radius in kpc

# Determine the dynamical mass of 47 Tuc
massTuc = WolfMass(sigmaTuc, reTuc)
print(massTuc)
massTuc

print(f" Mass to Light Ratio 47 Tuc: {np.around(massTuc/lumTuc,3)}")
# this is globular cluster

# Willman I
lumWI = 1e3*u.Lsun # luminosity
sigmaWI = 4.3*u.km/u.s # velocity dispersion
reWI  =  25/1000*u.kpc  # effective radius

# Compute the dynamical mass
massWI = WolfMass(sigmaWI,reWI)
print(massWI)
massWI

print(f" Mass to Light Ratio Willman 1: {np.around(massWI/lumWI,3):}")
# this is a galaxy

# # Part B :  Stellar to Halo Mass Relation
#
# Following the work of [Moster et al. 2013 (MNRAS, 428, 3121)](https://ui.adsabs.harvard.edu/abs/2013MNRAS.428.3121M/abstract)
#
#
# `Equation 2:`                  $ \frac{m}{M} = 2N \left [ \left ( \frac{M}{M_1} \right)^{-\beta} + \left (\frac{M}{M_1} \right)^{\gamma} \right]$
#
# $m$ = stellar mass, $M$ = halo mass
#
# `Equation 11:`        log $M_1(z) = M_{10} + M_{11} \frac{z}{z+1} $
#
# `Equation 12:`        $N(z) = N_{10} + N_{11} \frac{z}{z+1} $
#
# `Equation 13:`         $\beta(z) = \beta_{10} + \beta_{11} \frac{z}{z+1} $
#
# `Equation 14:`         $\gamma(z) = \gamma_{10} + \gamma_{11} \frac{z}{z+1} $

# # Q1
#
# Modify the class below by adding a function that takes the `SHMratio` and returns the stellar mass.

class AbundanceMatching:
    """ Class to define the abundance matching relations from
    Moster et al. 2013"""

    def __init__(self, mhalo, z):
        """ Initialize the class
        PARAMETERS
        ----------
            mhalo: float
                Halo mass in Msun
            z: float
                redshift
        """

        #initializing the parameters:
        self.mhalo = mhalo # Halo Mass in Msun
        self.z = z  # Redshift

    def logM1(self):
        """eq. 11 of Moster 2013
        OUTPUT:
            M1: float
                characteristic mass in log(Msun)
        """
        M10      = 11.59
        M11      = 1.195
        return M10 + M11*(self.z/(1+self.z))

    def N(self):
        """eq. 12 of Moster 2013
        OUTPUT:
            Normalization for eq. 2
        """
        N10      = 0.0351
        N11      = -0.0247

        return N10 + N11*(self.z/(1+self.z))

    def Beta(self):
        """eq. 13 of Moster 2013
        OUTPUT:  power of the low mass slope"""
        beta10      = 1.376
        beta11      = -0.826

        return beta10 + beta11*(self.z/(1+self.z))

    def Gamma(self):
        """eq. 14 of Moster 2013
        OUTPUT: power of the high mass slope """
        gamma10      = 0.608
        gamma11      = 0.329

        return gamma10 + gamma11*(self.z/(1+self.z))

    def SHMratio(self):
        """
        eq. 2 of Moster + 2013
        OUTPUT:
            SHMratio float
                Stellar mass to halo mass ratio
        """
        M1 = 10**self.logM1() # Converting characteristic mass
        # to Msun from Log(Msun)
        A = (self.mhalo/M1)**(-self.Beta())  # Low mass end
        B = (self.mhalo/M1)**(self.Gamma())   # High mass end
        Norm = 2*self.N() # Normalization

        SHMratio = Norm*(A+B)**(-1)

        return SHMratio

 # Q1: add a function to the class that takes the SHM ratio and returns
# The stellar mass

    def StellarMass(self):
        """ Method to compute the stellar mass
        using eq. 2 of Moster + 2013

        OUTPUT: float, stellar mass in Msun"""

        return self.mhalo*self.SHMratio()

# # Part C : Plot the Moster Relation
#
# Reproduce the below figure from Moster + 2013
# Plot this for z=0, 0.5, 1, 2
#
# ![mos](./MosterFig.png)

mh = np.logspace(10,15,1000) # Logarithmically spaced array

# Define Instances of the Class for each redshift
MosterZ0 = AbundanceMatching(mh,0)
MosterZ0_5 = AbundanceMatching(mh,0.5)
MosterZ1 = AbundanceMatching(mh,1)
MosterZ2 = AbundanceMatching(mh,2)

fig,ax = plt.subplots(figsize=(10,8))

#adjust tick label font size
label_size = 22
matplotlib.rcParams['xtick.labelsize'] = label_size
matplotlib.rcParams['ytick.labelsize'] = label_size

# Plot z = 0
plt.plot(np.log10(mh), np.log10(MosterZ0.StellarMass()),
         linewidth = 5, label='z=0')
# Plot z = 0.5
plt.plot(np.log10(mh), np.log10(MosterZ0_5.StellarMass()),
         linewidth = 5, linestyle='-.', label = 'z=0.5')
# Plot z = 1
plt.plot(np.log10(mh), np.log10(MosterZ1.StellarMass()),
         linewidth = 5, linestyle='--', label = 'z=1')
# Plot z = 2
plt.plot(np.log10(mh), np.log10(MosterZ2.StellarMass()),
         linewidth = 5, linestyle=':', label='z=2')

# Axes labels
plt.xlabel('log (M$_h$/M$_\odot$)',fontsize=22)
plt.ylabel('log (m$_\star$/M$_\odot$)', fontsize=22)

# Legend
plt.legend(loc='lower right',fontsize='x-large')

# save the file
plt.savefig(AbundanceMatching_Lab5.png)

# At every halo mass, the stellar mass is lower backwards in time.
# The characteristic stellar mass is higher at higher redshift
# -- so massive things do form early, then the smaller
# systems will catch up
# Lowest stellar mass is 1e8

# # Part D
#
# # Q1
#
# In traditional models of the Magellanic Clouds (prior to 2010), the LMC is thought to have a halo mass of order $3 \times 10^{10}$ M$_\odot$.  According to LCDM theory, what should be the stellar mass of such a halo at z=0?
#
# How does this compare against the actual observed stellar mass of the LMC at the present day of $3 \times 10^9$ M$_\odot$ ?
#
# What is the $\Lambda$CDM expected halo mass? What is the origin of any discrepancy?

# LMC halo mass
haloLMC1 = 3e10 # original halo mass

# Create a class object
LMC1 = AbundanceMatching(haloLMC1,0)

# Use LMC object to determine its stellar mass
print(f"Expected M* of LMC: {np.round(LMC1.StellarMass()/1e9,3)}(1e9 Msun)")

# Compare against the real stellar mass
starLMC = 3e9
print(f"Percentage of observed M*: {np.round(LMC1.StellarMass()/starLMC,3)*100}%")

# So what should the LMC's halo mass be to give a stellar mass of 3e9?
# Start with a guess
haloLMC2 = 1.65e11

LMC2 = AbundanceMatching(haloLMC2,0)
print(f"Expected Stellar Mass of LMC {np.round(LMC2.StellarMass()/1e9,3)}  (1e9 Msun)")

# LEsson: the Halo mass relation only returns the halo mass of CENTRALS

# # Q2
#
# What is the expected stellar mass of an L* galaxy at z=0?
#
# What is the expected stellar mass of an L* galaxy at z = 2?

print(f'Log M1, characteristic halo mass at z=0: {MosterZ0.logM1()}')
MstarZ0 = AbundanceMatching(10**MosterZ0.logM1(),0)

print(f'Stellar mass of L* galaxy at z=0: {np.around(MstarZ0.StellarMass()/1e10,2)} (1e10 Msun) ')

print(f'Log M1, characteristic halo mass at z=2: {MosterZ2.logM1()}')
MstarZ2 = AbundanceMatching(10**MosterZ2.logM1(),0)

print(f'Stellar mass of L* galaxy at z=2: {np.around(MstarZ2.StellarMass()/1e10,2)} (1e10 Msun) ')


import pygame
import time
import code_tic_tac_toe as tic
import sys
pygame.init()
size = width, height = 610, 410
white = (255, 255, 255)
violet = (127, 0, 255)
black = (0, 0, 0)
screen = pygame.display.set_mode(size)
mf = pygame.font.SysFont('arial', 20)
lf = pygame.font.SysFont('arial', 40)
mof = pygame.font.SysFont('arial', 60)
user = None
board = tic.initial_state()
ai_turn = False
while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()
    screen.fill(violet)
    if user is None:
        title = lf.render("Let's Play Tic-Tac-Toe", True, white)
        titleRect = title.get_rect()
        titleRect.center = ((width / 2), 60)
        screen.blit(title, titleRect)

        title1 = mf.render("please select your character to start the game", True, black)
        title1Rect = title1.get_rect()
        title1Rect.center = ((width / 2), 95)
        screen.blit(title1, title1Rect)

        title2 = mf.render("AI - Case Study", True, black)
        title2Rect = title1.get_rect()
        title2Rect.center = ((width / 2), 370)
        screen.blit(title2, title2Rect)

        plyXbtn = pygame.Rect((width / 8), (height / 2), width / 4, 90)
        playX = mf.render("X", True, black)
        playXRect = playX.get_rect()
        playXRect.center = plyXbtn.center
        pygame.draw.rect(screen, white, plyXbtn)
        screen.blit(playX, playXRect)

        plyObtn = pygame.Rect(5 * (width / 8), (height / 2), width / 4, 90)
        playO = mf.render("O", True, black)
        playORect = playO.get_rect()
        playORect.center = plyObtn.center
        pygame.draw.rect(screen, white, plyObtn)
        screen.blit(playO, playORect)
        click, _, _ = pygame.mouse.get_pressed()
        if click == 1:
            mouse = pygame.mouse.get_pos()
            if plyXbtn.collidepoint(mouse):
                time.sleep(0.2)
                user = tic.X
            elif plyObtn.collidepoint(mouse):
                time.sleep(0.2)
                user = tic.O
    else:
        tile_size = 80
        tile_origin = (width / 2 - (1.5 * tile_size),
                       height / 2 - (1.5 * tile_size))
        tiles = []
        for i in range(3):
            row = []
            for j in range(3):
                rect = pygame.Rect(
                    tile_origin[0] + j * tile_size,
                    tile_origin[1] + i * tile_size,
                    tile_size, tile_size
                )
                pygame.draw.rect(screen, white, rect, 3)

                if board[i][j] != tic.EMPTY:
                    move = mof.render(board[i][j], True, white)
                    moveRect = move.get_rect()
                    moveRect.center = rect.center
                    screen.blit(move, moveRect)
                row.append(rect)
            tiles.append(row)

        game_over = tic.terminal(board)
        player = tic.player(board)
        if game_over:
            winner = tic.winner(board)
            if winner is None:
                title = f"DRAW!."
            else:
                title = f"Game Over: {winner} WINS!."
        elif user == player:
            title = f"Play as {user}"
        else:
            title = f""
        title = lf.render(title, True, white)
        titleRect = title.get_rect()
        titleRect.center = ((width / 2), 30)
        screen.blit(title, titleRect)
        if user != player and not game_over:
            if ai_turn:
                time.sleep(0.5)
                move = tic.mini_max(board)
                board = tic.result(board, move)
                ai_turn = False
            else:
                ai_turn = True
        click, _, _ = pygame.mouse.get_pressed()
        if click == 1 and user == player and not game_over:
            mouse = pygame.mouse.get_pos()
            for i in range(3):
                for j in range(3):
                    if (board[i][j] == tic.EMPTY and tiles[i][j].collidepoint(mouse)):
                        board = tic.result(board, (i, j))
        if game_over:
            againButton = pygame.Rect(width / 3, height - 65, width / 3, 50)
            again = mf.render("Restart Game", True, black)
            againRect = again.get_rect()
            againRect.center = againButton.center
            pygame.draw.rect(screen, white, againButton)
            screen.blit(again, againRect)
            click, _, _ = pygame.mouse.get_pressed()
            if click == 1:
                mouse = pygame.mouse.get_pos()
                if againButton.collidepoint(mouse):
                    time.sleep(0.2)
                    user = None
                    board = tic.initial_state()
                    ai_turn = False

    pygame.display.flip()


import pandas as pd
import pytesseract.pytesseract
import requests as req
from bs4 import BeautifulSoup
from urllib.parse import urlparse
import urllib.request as urlreq
from pdf2image import convert_from_path
import os
import cv2
from PIL import Image
import json

'''---------------------1. READING DATA FROM EXCEL FILE-----------------------------------'''

def read_data(filename):
    # Reading Excel File
    data = pd.read_excel(filename, header=None)

    # Converting dataframe to list
    list_of_links = data.iloc[:, 0].values

    return list_of_links

'''---------------------2. FETCHING CONTENTS FROM LINKS AND STORING-----------------------'''

def pdf_download(list_of_links):
    opener = urlreq.build_opener()
    opener.addheaders = [('User-agent', 'Mozilla/5.0')]
    urlreq.install_opener(opener)

    i = 0
    for link in list_of_links:
        if '.pdf' in link:
            urlreq.urlretrieve(link, f"Downloaded_pdfs/pdf{i}.pdf")

        else:
            domain = "https://" + urlparse(link).netloc
            response = req.get(link)
            extracted_html = BeautifulSoup(response.text, 'html.parser')
            links_in_html = extracted_html.find_all('a')
            for sublink in links_in_html:
                if '.pdf' in sublink.get('href', []):
                    urlreq.urlretrieve(domain+sublink.get('href'), f"Downloaded_pdfs/pdf{i}.pdf")
                    break

        print(f"Downloaded PDF {i}")
        i += 1

'''---------------------3. CONVERTING PDFS TO IMAGES--------------------------------------'''

def pdf_to_images(path_to_pdf_directory, path_to_poppler_bin):
    for i in range(48):
        pages = convert_from_path(path_to_pdf_directory+f"\\pdf{i}.pdf", 300,
                                  poppler_path=path_to_poppler_bin)
        j = 0
        new_path = os.path.join("PDF_Images", f"Pdf{i}")
        os.mkdir(new_path)
        for page in pages:
            img_path = new_path + f"\\Pic_{j}.jpg"
            page.save(img_path, "JPEG")
            j += 1
        print(f"PDF {i} Done")

'''---------------------4. PROCESSING IMAGES-------------------------------------------'''

def process_pdf_images(list_of_pdf_links, path_to_pdf_images_dir, path_to_tesseract_exe, path_to_tessdata,
                       language="en", columns=False):
    pytesseract.pytesseract.tesseract_cmd = path_to_tesseract_exe
    tessdata_config = f'--tessdata-dir "{path_to_tessdata}" '
    if columns == True:
        tessdata_config += '--psm 3'
    else:
        tessdata_config += '--psm 6'

    json_content = []

    dirs = os.listdir(path_to_pdf_images_dir)
    dirs.sort(key=lambda x: int(x[3:]))

    for (i, dir) in enumerate(dirs):
        sub_dir = path_to_pdf_images_dir + "\\" + dir
        files = os.listdir(sub_dir)
        files.sort(key=lambda x: int(x[4:-4]))

        j, c, f = 0, 0, 0
        for file in files:
            img_path = sub_dir + "\\" + file
            img = cv2.imread(img_path)
            gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
            thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
            tmp_file = f"{f}.png"
            f += 1
            cv2.imwrite(tmp_file, thresh)
            text = pytesseract.image_to_string(Image.open(tmp_file), lang=language, config=tessdata_config)
            os.remove(tmp_file)
            text = text.replace('\n', '')

            content = {}

            if ".pdf" in list_of_pdf_links[i]:
                content["pdf-url"] = list_of_pdf_links[i]
                content["page-url"] = content["pdf-url"]
            else:
                link = list_of_pdf_links[i]
                if "page" in link:
                    base_link = link.split("page")[0]
                else:
                    base_link = link.split("mode")[0]

                content["pdf-url"] = base_link + "mode/2up"
                if j == 0:
                    content["page-url"] = content["pdf-url"]
                    j += 1
                else:
                    content["page-url"] = base_link + f"page/n{j}/mode/2up"
                    c ^= 1
                    if c == 0:
                        j += 2

            content["pdf-content"] = text

            json_content.append(content)

        print(f"PDF {i} processed!")

    return json_content

'''---------------------5. PREPARING JSON FILE--------------------------------------------'''

def save_json_file(filename, json_content):
    with open(filename, "w") as file:
        json.dump(json_content, file, indent=2, ensure_ascii=False)

'''---------------------MAIN FUNCTION-----------------------------------------------------'''

def main():
    # ADD PATH of excel sheet
    filename = ''

    list_of_pdf_links = read_data(filename)

    pdf_download(list_of_links=list_of_pdf_links)

    # ADD PATH to "Downloaded_pdfs"
    path_to_pdf_directory = ""

    # ADD PATH to poppler bin folder
    path_to_poppler_bin = ""

    pdf_to_images(path_to_pdf_directory=path_to_pdf_directory, path_to_poppler_bin=path_to_poppler_bin)

    # ADD PATH to "PDF_Images"
    path_to_pdf_images_dir = ""

    # ADD PATH to "tesseract.exe"
    path_to_tesseract_exe = ""

    # ADD PATH to tessdata folder
    path_to_tessdata = ""

    # ADD language to be used
    language = ""

    json_content = process_pdf_images(list_of_pdf_links = list_of_pdf_links,
                                      path_to_pdf_images_dir=path_to_pdf_images_dir,
                                      path_to_tesseract_exe=path_to_tesseract_exe,
                                      path_to_tessdata=path_to_tessdata,
                                      language=language,
                                      columns=True)

    save_json_file(filename="pdf_extract.json", json_content=json_content)

if __name__ == "__main__":
    main()


#!/usr/bin/env python3

import json
import os
import re
import requests
import shutil
import sqlite3
import shlex
import subprocess
import sys
import threading
import time
import zipfile

from cpe_search.cpe_search import update as update_cpe

NVD_DATAFEED_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "nvd_data_feeds")
VULNDB_FILE = os.path.join(os.path.dirname(os.path.realpath(__file__)), "vulndb.db3")
VULNDB_BACKUP_FILE = os.path.join(os.path.dirname(os.path.realpath(__file__)), "vulndb.db3.bak")
VULNDB_ARTIFACT_URL = "https://github.com/ra1nb0rn/search_vulns/releases/latest/download/vulndb.db3"
CVE_EDB_MAP_FILE = os.path.join(os.path.dirname(os.path.realpath(__file__)), "cveid_to_edbid.json")
CPE_DICT_FILE = os.path.join(os.path.dirname(os.path.realpath(__file__)), "cpe_search/cpe-search-dictionary_v2.3.csv")
CPE_DICT_ARTIFACT_URL = "https://github.com/ra1nb0rn/search_vulns/releases/latest/download/cpe-search-dictionary_v2.3.csv"
POC_IN_GITHUB_REPO = "https://github.com/nomi-sec/PoC-in-GitHub.git"
POC_IN_GITHUB_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "PoC-in-GitHub")
REQUEST_HEADERS = {"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/62.0"}
NVD_UPDATE_SUCCESS = None
QUIET = False

def update_vuln_db():
    """Update the vulnerability database"""

    global NVD_UPDATE_SUCCESS

    if os.path.isfile(VULNDB_FILE):
        shutil.move(VULNDB_FILE, VULNDB_BACKUP_FILE)

    # start CVE ID <--> EDB ID mapping creation in background thread
    cve_edb_map_thread = threading.Thread(target=create_cveid_edbid_mapping)
    cve_edb_map_thread.start()

    # download NVD datafeeds from https://nvd.nist.gov/vuln/data-feeds
    if os.path.exists(NVD_DATAFEED_DIR):
        shutil.rmtree(NVD_DATAFEED_DIR)
    os.makedirs(NVD_DATAFEED_DIR)

    if not QUIET:
        print('[+] Downloading NVD data feeds and EDB information')

    # first download the data feed overview to retrieve URLs to all data feeds
    try:
        nvd_response = requests.get("https://nvd.nist.gov/vuln/data-feeds", timeout=20)
    except:
        NVD_UPDATE_SUCCESS = False
        communicate_warning('An error occured when trying to download webpage: https://nvd.nist.gov/vuln/data-feeds')
        rollback()
        cve_edb_map_thread.join()
        return 'An error occured when trying to download webpage: https://nvd.nist.gov/vuln/data-feeds'
    if nvd_response.status_code != requests.codes.ok:
        NVD_UPDATE_SUCCESS = False
        rollback()
        cve_edb_map_thread.join()
        return 'An error occured when trying to download webpage: https://nvd.nist.gov/vuln/data-feeds'

    # match the data feed URLs
    nvd_nist_datafeed_html = nvd_response.text
    jfeed_expr = re.compile(r"/feeds/json/cve/1\.1/nvdcve-1\.1-\d\d\d\d.json\.zip")
    nvd_feed_urls = re.findall(jfeed_expr, nvd_nist_datafeed_html)

    if not nvd_feed_urls:
        NVD_UPDATE_SUCCESS = False
        communicate_warning('No data feed links available on https://nvd.nist.gov/vuln/data-feeds')
        rollback()
        cve_edb_map_thread.join()
        return 'No data feed links available on https://nvd.nist.gov/vuln/data-feeds'

    # download all data feeds
    with open(os.devnull, "w") as outfile:
        zipfiles = []
        for nvd_feed_url in nvd_feed_urls:
            nvd_feed_url = "https://nvd.nist.gov" + nvd_feed_url
            outname = os.path.join(NVD_DATAFEED_DIR, nvd_feed_url.split("/")[-1])
            return_code = subprocess.call("wget %s -O %s" % (shlex.quote(nvd_feed_url), shlex.quote(outname)),
                                          stdout=outfile, stderr=subprocess.STDOUT, shell=True)
            if return_code != 0:
                NVD_UPDATE_SUCCESS = False
                communicate_warning('Retrieving NVD data feed %s failed' %  nvd_feed_url)
                rollback()
                cve_edb_map_thread.join()
                return 'Retrieving NVD data feed %s failed' %  nvd_feed_url
            zipfiles.append(outname)

    if os.path.isfile("wget-log"):
        os.remove("wget-log")

    # unzip data feeds
    if not QUIET:
        print("[+] Unzipping data feeds")

    for file in zipfiles:
        try:
            zip_ref = zipfile.ZipFile(file, "r")
            zip_ref.extractall(NVD_DATAFEED_DIR)
            zip_ref.close()
            os.remove(file)
        except:
            NVD_UPDATE_SUCCESS = False
            communicate_warning('Unzipping data feed %s failed' % file)
            rollback()
            cve_edb_map_thread.join()
            return 'Unzipping data feed %s failed' % file

    # build local NVD copy with downloaded data feeds
    print('[+] Building vulnerability database')
    create_db_call = ["./create_db", NVD_DATAFEED_DIR, VULNDB_FILE]
    with open(os.devnull, "w") as outfile:
        return_code = subprocess.call(create_db_call, stdout=outfile, stderr=subprocess.STDOUT)

    if return_code != 0:
        NVD_UPDATE_SUCCESS = False
        communicate_warning('Building NVD database failed')
        rollback()
        cve_edb_map_thread.join()
        return 'Building NVD database failed'

    shutil.rmtree(NVD_DATAFEED_DIR)
    NVD_UPDATE_SUCCESS = True
    cve_edb_map_thread.join()

    # build and add table of PoC-in-GitHub
    print('[+] Adding PoC-in-GitHub information')
    try:
        create_poc_in_github_table()
    except:
        communicate_warning('Building PoCs in GitHub table failed')
        rollback()
        return 'Building PoCs in GitHub table failed'

    # remove backup file on success
    if os.path.isfile(VULNDB_BACKUP_FILE):
        os.remove(VULNDB_BACKUP_FILE)

def rollback():
    """Rollback the DB / module update"""

    communicate_warning('An error occured, rolling back database update')
    if os.path.isfile(VULNDB_FILE):
        os.remove(VULNDB_FILE)
    if os.path.isfile(VULNDB_BACKUP_FILE):
        shutil.move(VULNDB_BACKUP_FILE, VULNDB_FILE)
    if os.path.isdir(NVD_DATAFEED_DIR):
        shutil.rmtree(NVD_DATAFEED_DIR)

def communicate_warning(msg: str):
    """Communicate warning via logger or stdout"""

    if not QUIET:
        print("Warning: " + msg)

def get_all_edbids():
    """ Return a list of all existing EDB IDs """
    files_exploits_resp = requests.get("https://raw.githubusercontent.com/offensive-security/exploitdb/master/files_exploits.csv", headers=REQUEST_HEADERS)
    files_exploits = files_exploits_resp.text
    return re.findall(r"^(\d+),.*$", files_exploits, re.MULTILINE)

def create_cveid_edbid_mapping():
    """ Create a CVE ID --> EDB ID mapping and store in local vuln DB """

    # get all unknown EDB IDs
    all_edbids, edbids_no_cveid = set(get_all_edbids()), set()
    if os.path.isfile(CVE_EDB_MAP_FILE):
        with open(CVE_EDB_MAP_FILE) as f:
            cve_edb_map = json.load(f)
        mapped_edbids = set()
        edbids_no_cveid = set(cve_edb_map["N/A"])
        for ebdids in cve_edb_map.values():
            mapped_edbids |= set(ebdids)
        remaining_edbids = all_edbids - mapped_edbids
    elif os.path.isfile(VULNDB_BACKUP_FILE):
        # if the DB is updated and previously built map is not available, build
        # incremental CVE ID --> EDB ID mapping based on previous version of DB
        cve_edb_map = recover_map_data_from_db()
        mapped_edbids = set()
        for edbids in cve_edb_map.values():
            mapped_edbids |= set(edbids)
        gh_map_edb_cve_resp = requests.get("https://raw.githubusercontent.com/andreafioraldi/cve_searchsploit/master/cve_searchsploit/exploitdb_mapping.json", headers=REQUEST_HEADERS)
        edb_cve_map = json.loads(gh_map_edb_cve_resp.text)
        edbids_no_cveid = set()
        for edbid, cveids in edb_cve_map.items():
            if not cveids:
                edbids_no_cveid.add(edbid)
        remaining_edbids = all_edbids - mapped_edbids - edbids_no_cveid
    else:
        # if a full DB installation is done, build CVE ID --> EDB ID mapping from scratch
        # use the mapping data from andreafioraldi's cve_searchsploit as base
        gh_map_cve_edb_resp = requests.get("https://raw.githubusercontent.com/andreafioraldi/cve_searchsploit/master/cve_searchsploit/exploitdb_mapping_cve.json", headers=REQUEST_HEADERS)
        cve_edb_map = json.loads(gh_map_cve_edb_resp.text)
        gh_map_edb_cve_resp = requests.get("https://raw.githubusercontent.com/andreafioraldi/cve_searchsploit/master/cve_searchsploit/exploitdb_mapping.json", headers=REQUEST_HEADERS)
        edb_cve_map = json.loads(gh_map_edb_cve_resp.text)
        edbids_no_cveid = set()
        for edbid, cveids in edb_cve_map.items():
            if not cveids:
                edbids_no_cveid.add(edbid)
        gh_captured_edbids = set(edb_cve_map.keys())
        remaining_edbids = all_edbids - gh_captured_edbids

    # manually crawl the mappings not yet created
    cveid_expr = re.compile(r"https://nvd.nist.gov/vuln/detail/(CVE-\d\d\d\d-\d+)")
    pause_time, last_time, printed_wait = 0.51, time.time(), False
    for i, edbid in enumerate(remaining_edbids):
        # wait at least pause_time seconds before making another request to not put heavy load on servers
        time_diff = time.time() - last_time
        if time_diff < pause_time:
            time.sleep(pause_time - time_diff)
        last_time = time.time()

        if not QUIET and NVD_UPDATE_SUCCESS and not printed_wait:
            print("Waiting for CVE ID <--> EDB ID map creation to finish ...")
            printed_wait = True
        elif (NVD_UPDATE_SUCCESS is not None) and (not NVD_UPDATE_SUCCESS):
            return

        exploit_page_resp = requests.get("https://www.exploit-db.com/exploits/%s" % edbid, headers=REQUEST_HEADERS, timeout=30)
        cveids = cveid_expr.findall(exploit_page_resp.text)

        if not cveids:
            edbids_no_cveid.add(edbid)

        for cveid in cveids:
            if cveid not in cve_edb_map:
                cve_edb_map[cveid] = []
            cve_edb_map[cveid].append(edbid)

    cve_edb_map["N/A"] = list(edbids_no_cveid)  # store all EDBIDs without CVE
    with open(CVE_EDB_MAP_FILE, "w") as f:
        f.write(json.dumps(cve_edb_map))

    while NVD_UPDATE_SUCCESS is None:
        time.sleep(1)

    if NVD_UPDATE_SUCCESS:
        fill_database_with_mapinfo(cve_edb_map)

def fill_database_with_mapinfo(cve_edb_map):
    """ Put the given mapping data into the database specified by the given cursor """

    db_conn = sqlite3.connect(VULNDB_FILE)
    db_cursor = db_conn.cursor()

    update_statement = "UPDATE cve SET edb_ids=? WHERE cve_id=?"
    for cveid, edbids in cve_edb_map.items():
        if cveid == "N/A":  # skip the fake item holding EDBIDs without CVEID
            continue

        edbids = sorted(set(edbids), key=lambda eid: int(eid))
        db_cursor.execute(update_statement, (",".join(edbids), cveid))

    db_conn.commit()
    db_conn.close()

def recover_map_data_from_db():
    """ Return stored CVE ID <--> EDB ID mapping from DB backup file """

    db_conn = sqlite3.connect(VULNDB_BACKUP_FILE)
    db_cursor = db_conn.cursor()

    # recover CVE ID --> EDB ID data
    db_cursor.execute("SELECT cve_id, edb_ids FROM cve")
    map_data = db_cursor.fetchall()
    cve_edb_map = {}
    for cve_id, edb_ids in map_data:
        if edb_ids:
            cve_edb_map[cve_id] = edb_ids.split(",")

    db_conn.close()
    return cve_edb_map

def create_poc_in_github_table():
    """ Create CVE ID <--> GitHub PoC URL mapping"""

    if os.path.isdir(POC_IN_GITHUB_DIR):
        shutil.rmtree(POC_IN_GITHUB_DIR)

    # download PoC in GitHub Repo
    return_code = subprocess.call(
        "git clone --depth 1 %s '%s'"
        % (POC_IN_GITHUB_REPO, POC_IN_GITHUB_DIR),
        shell=True,
        stderr=subprocess.DEVNULL
    )
    if return_code != 0:
        raise (Exception("Could not download latest resources of PoC-in-GitHub"))

    # add PoC / exploit information to DB
    db_conn = sqlite3.connect(VULNDB_FILE)
    db_cursor = db_conn.cursor()
    db_cursor.execute('CREATE TABLE cve_poc_in_github_map (cve_id VARCHAR(25), reference text, PRIMARY KEY (cve_id, reference));')
    db_conn.commit()

    for file in os.listdir(POC_IN_GITHUB_DIR):
        yearpath = os.path.join(POC_IN_GITHUB_DIR, file)
        if not os.path.isdir(yearpath):
            continue
        try:
            int(file)
        except:
            continue

        for cve_file in os.listdir(yearpath):
            cve_filepath = os.path.join(yearpath, cve_file)
            cve_id = os.path.splitext(cve_file)[0]
            with open(cve_filepath) as cve_fh:
                cve_json = json.loads(cve_fh.read())
                for poc_item in cve_json:
                    db_cursor.execute('INSERT INTO cve_poc_in_github_map VALUES (?, ?)', (cve_id, poc_item['html_url']))

    db_conn.commit()
    db_conn.close()

    if os.path.isdir(POC_IN_GITHUB_DIR):
        shutil.rmtree(POC_IN_GITHUB_DIR)

def run(full=False):
    if full:
        print("[+] Updating stored software information")
        update_cpe("2.3")
        print("[+] Updating vulnerability database")
        error = update_vuln_db()
        if error:
            print(error)
            sys.exit(1)
    else:
        print("[+] Downloading latest versions of resources ...")
        if os.path.isfile(CPE_DICT_FILE):
            shutil.move(CPE_DICT_FILE, CPE_DICT_FILE+".bak")
        if os.path.isfile(VULNDB_FILE):
            shutil.move(VULNDB_FILE, VULNDB_BACKUP_FILE)

        try:
            quiet_flag = ""
            if QUIET:
                quiet_flag = "-q"
            else:
                quiet_flag = "-q --show-progress"

            return_code = subprocess.call("wget %s %s -O %s" % (quiet_flag, shlex.quote(CPE_DICT_ARTIFACT_URL),
                                          shlex.quote(CPE_DICT_FILE)), shell=True)
            if return_code != 0:
                raise(Exception("Could not download latest resource files"))
            return_code = subprocess.call("wget %s %s -O %s" % (quiet_flag, shlex.quote(VULNDB_ARTIFACT_URL),
                                          shlex.quote(VULNDB_FILE)), shell=True)
            if return_code != 0:
                raise(Exception("Could not download latest resource files"))

            if os.path.isfile(CPE_DICT_FILE+".bak"):
                os.remove(CPE_DICT_FILE+".bak")
            if os.path.isfile(VULNDB_BACKUP_FILE):
                os.remove(VULNDB_BACKUP_FILE)
        except Exception as e:
            print("[!] Encountered an error: %s" % str(e))
            if os.path.isfile(CPE_DICT_FILE+".bak"):
                shutil.move(CPE_DICT_FILE+".bak", CPE_DICT_FILE)
                print("[+] Restored software infos from backup")
            if os.path.isfile(VULNDB_BACKUP_FILE):
                shutil.move(VULNDB_BACKUP_FILE, VULNDB_FILE)
                print("[+] Restored vulnerability infos from backup")

if __name__ == "__main__":
    if len(sys.argv) > 1 and sys.argv[1] == "--full":
        run(True)
    else:
        run(False)


# Discord
import discord
from discord.ext import commands
from discord import app_commands

# Other
import asyncio
from typing import Optional

# My import
from classes.discordbot import DiscordBot
from classes.utils import COLORS

class Info(commands.GroupCog, name="info", group_name="info", group_description="Pour gérer les votes !"):
    def __init__(self, bot: DiscordBot):
        self.bot = bot

    @app_commands.command(name="server", description="Les informations du serveur.")
    @app_commands.guild_only()
    async def server_info(self, interaction: discord.Interaction):
        await interaction.response.defer(thinking=True)
        await asyncio.sleep(1)

        server_name = interaction.guild.name

        nbr_members = interaction.guild.member_count

        nbr_roles = len(interaction.guild.roles) - 1

        channels = interaction.guild.channels
        nbr_txt_channels = 0
        nbr_voc_channels = 0
        for channel in channels:
            if isinstance(channel, discord.TextChannel):
                nbr_txt_channels += 1
            if isinstance(channel, discord.VoiceChannel):
                nbr_voc_channels += 1

        nbr_roles = len(interaction.guild.roles)

        owner = interaction.guild.owner.mention

        afk_channel = interaction.guild.afk_channel
        if isinstance(afk_channel, discord.VoiceChannel):
            afk_channel = afk_channel.mention
        else:
            afk_channel = '`non défini`'

        date_creation = interaction.guild.created_at

        url_server_image = interaction.guild.icon.url

        # Generate Embed
        dict_embed = {
            'title': f'Information Serveur',
            'description': f':white_small_square: Nom du serveur : **{server_name}**\n:white_small_square: **{nbr_members}** membres \n:white_small_square: **{nbr_txt_channels}** salons textuels \n:white_small_square: **{nbr_voc_channels}** salons vocaux \n:white_small_square: **{nbr_roles}** roles \n:white_small_square: {owner} est le propriétaire \n:white_small_square: Créé le **{date_creation.day}/{date_creation.month}/{date_creation.year}** \n:white_small_square: Salon AFK : {afk_channel}',
            'color': COLORS['white'],
            'thumbnail': {
                'url': url_server_image,
            }
        }
        embed = discord.Embed.from_dict(dict_embed)

        await interaction.followup.send(embed=embed)

    @app_commands.command(name="member", description="Les informations d'un membre.")
    @app_commands.describe(member='Les informations de qui ?')
    @app_commands.guild_only()
    async def member_info(self, interaction: discord.Interaction, member: Optional[discord.Member] = None):
        await interaction.response.defer(thinking=True)
        await asyncio.sleep(1)

        if member == None:
            member = interaction.user

        avatar_url = member.avatar.url
        discriminator = member.discriminator
        if discriminator == 0:
            discriminator = '`aucun`'
        else:
            discriminator = f'**{discriminator}**'

        flags = member.public_flags.all()
        badge = ''
        if len(flags) > 0:
            badge = ""
            for flag in flags:
                name = str(flag).split('.')[1]
                badge += f'`{name}` '
        else:
            badge = '`aucun`'

        # Generate Embed
        dict_embed = {
            'title': 'Information Membre',
            'description': f':white_small_square: Nom du membre : **{member.mention}**\n:white_small_square: Tag : {discriminator} \n\n:white_small_square: Badge(s) : {badge}',
            'color': COLORS['white'],
            'thumbnail': {
                'url': avatar_url,
            }
        }
        embed = discord.Embed.from_dict(dict_embed)

        await interaction.followup.send(embed=embed)

async def setup(bot: DiscordBot):
    await bot.add_cog(Info(bot))


#!/usr/bin/python
import spidev
import time
import os
import RPi.GPIO as GPIO

GPIO.setmode(GPIO.BOARD)
GPIO.setwarnings(False)

# Open SPI bus
spi = spidev.SpiDev()
spi.open(0, 0)

# Define GPIO to motor
DC_motor_a = 7
DC_motor_b = 11

GPIO.setup(DC_motor_a, GPIO.OUT)
GPIO.setup(DC_motor_b, GPIO.OUT)

# Define sensor channels
temp_channel = 0

# Timing constants
E_PULSE = 0.0005
E_DELAY = 0.0005
delay = 1

# Function to read SPI data from MCP3008 chip
# Channel must be an integer 0-7
def ReadChannel(channel):
    adc = spi.xfer2([1, (8 + channel) << 4, 0])
    data = ((adc[1] & 3) << 8) + adc[2]
    return data

while (1):
    light_level = ReadChannel(temp_channel)
    if (light_level < 100):
        lcd_string("Store off", LCD_LINE_2)
        GPIO.output(DC_motor_a, True)
        GPIO.output(DC_motor_b, FALSE)
        time.sleep(1)
    else:
        GPIO.output(DC_motor_b, TRUE)
        GPIO.output(DC_motor_a, FALSE)
        time.sleep(1)


import pytest
import magma as m

@pytest.mark.parametrize('T', [m.Bits[8], m.Tuple[m.Bit, m.Bits[8]]])
def test_aggregate_wireable_unwire(T):
    class Foo(m.Circuit):
        io = m.IO(I=m.In(T), O=m.Out(T))
        io.O @= io.I
        assert io.O.value() is io.I
        io.O.unwire(io.I)
        assert io.O.value() is None

        io.O @= io.I
        io.O[0]  # Trigger elaboration
        assert io.O.value() is io.I
        io.O.unwire(io.I)
        assert io.O.value() is None

@pytest.mark.skip("Slow test should only be run if needed")
def test_aggregate_wireable_recursion_error():
    class T(m.Product):
        x = m.Array[2, m.Bits[8]]
        y = m.Bit

    class Foo(m.Circuit):
        T = m.Array[1024, m.Bits[8]]
        io = m.IO(I=m.In(T), O=m.Out(T), S=m.In(m.Bit))
        x = io.I
        for i in range(256):
            prev = x
            x = T(name=f"asdf{i}")
            x @= prev
        io.O @= x

    repr(Foo)

    m.compile("build/Foo", Foo, output="mlir-verilog")


from hm import BaseList, Room
import xml.etree.ElementTree as ET

class RoomList(BaseList):
    def __init__(self, xml):
        BaseList.__init__(self, xml)
        self.rooms = []
        self.rebuild(xml)

    def rebuild(self, xml):
        self.rooms = []
        xmlp = ET.XMLParser(encoding=self.encoding)
        roomlist = ET.fromstring(xml, xmlp)

        for r in roomlist:
            room_name = r.attrib['name']
            ise_id = r.attrib['ise_id']
            channels = []
            for c in r:
                channels.append(c.attrib['ise_id'])
            new_room = Room(room_name, ise_id, channels)
            self.rooms.append(new_room)

    def print_all_rooms(self):
        for r in self.rooms:
            print(r)

    def get_room_count(self):
        return len(self.rooms)


from matplotlib import pyplot as plt
from pandasql import sqldf
import pandas as pd
import sqlite3

DB_PATH= r'database/spotify.db'
pysqldf = lambda q: sqldf(q, locals())

#################################################################################################
#                                                                                #
#                                         QUERIES                                               #
#                                                                                #
#################################################################################################

def query_total_entries_per_table(conn:sqlite3.Connection) -> pd.DataFrame:
    """
    Queries the total entries of users, streams, artists, releases and tracks.
    Parameters:
        conn (sqlite3.Connection): Databse connection
    Returns:
        result (pd.DataFrame): Total entries of users, streams, artists, releases and tracks.
    """
    return pd.read_sql_query("""
            SELECT  (
                        SELECT COUNT( DISTINCT user_name)
                        FROM stream
                    ) AS user_count,
                    (
                        SELECT COUNT(1)
                        FROM stream
                    ) AS stream_count,
                    (
                        SELECT COUNT(1)
                        FROM artist
                    ) AS artist_count,
                    (
                        SELECT COUNT(1)
                        FROM release
                    ) AS release_count,
                    (
                        SELECT COUNT(1)
                        FROM track
                    ) AS track_count""",conn)

def query_null_entry_statistic_on_columns(conn:sqlite3.Connection, table:str, columns:set) -> pd.DataFrame:
    """
    Queries the occurrence of null values in the column of a table in percent.
    Parameters:
        conn (sqlite3.Connection): Databse connection
        table (str): Name of the table, that gets queried.
        columns (set): Columns, to query at.
    Returns:
        result (pd.DataFrame):
    """
    frames = []
    for column in columns:
        null_statistic = pd.read_sql_query(f"""
                SELECT
                    ( printf( "%.3f" , 100.0*(COUNT(*)-COUNT({column}))/COUNT(*)) ) AS {column}
                FROM {table}; """, conn)

        frames.append( pd.melt( frame=null_statistic,
                                id_vars=None,
                                value_vars=null_statistic.columns,
                                value_name='null_values_in_percent',
                                var_name='column'))

        result = pd.concat(frames)
        result = result.set_index('column')
    return result

def query_active_users_over_weeks_and_weekdays_by_year(conn:sqlite3.Connection, year: str) -> pd.DataFrame:
    """
    Queries active users distributed over calendar weeks and weekdays based on a defined year.
    Parameters:
        conn (sqlite3.Connection): Databse connection
        year (str): The year for which the data is to be queried
    """
    return pd.read_sql_query(f"""
            SELECT   COUNT( DISTINCT lf.user_name) AS active_user, week, weekday, day_of_week, year
            FROM     stream lf
                        INNER JOIN time t on lf.timestamp_unix_id == t.timestamp_unix_id
            WHERE year = '{year}'
            GROUP BY year, week, day_of_week""", conn)

def query_streams_over_weekdays(conn:sqlite3.Connection) -> pd.DataFrame:
    """
    Queries the average streams over weekdays by year.
    Parameters:
        conn (sqlite3.Connection): Databse connection
    """
    stream_count_weekday_by_week = pd.read_sql_query(f"""
            SELECT year, week, weekday, COUNT(1) AS streams
            FROM stream lf
                    INNER JOIN time t ON lf.timestamp_unix_id == t.timestamp_unix_id
            GROUP BY year, week, weekday""", conn)

    return sqldf("""
            SELECT weekday, AVG(streams ) OVER(PARTITION BY weekday ) AS avg_streams, year
            FROM stream_count_weekday_by_week
            GROUP BY weekday""")

def query_entire_table(conn:sqlite3.Connection, table_name: str) -> pd.DataFrame:
    """
    Queries the entire table, based on table_name.
    Parameters:
        conn (sqlite3.Connection): Databse connection
        table_name (str): Name of the table, that gets queried.
    Returns:
        result (pd.DataFrame): Total entries of users, streams, artists, releases and tracks.
    """
    return pd.read_sql_query(f"SELECT  * from {table_name}",conn)

def weekly_trend_track(conn:sqlite3.Connection, top_n:str = "5") -> pd.DataFrame:
    """
    Queries the weekly top n ranked tracks and the positoin changes to prev week.
    Parameters:
        conn (sqlite3.Connection): Databse connection
        top_n (str): Amount of top tracks per week
    Returns:
        result (pd.DataFrame): dataframe with the query output
    """
    top_track_weekly_rank_table = pd.read_sql_query("""
	        SELECT   year, week, track_name, artist_name, lf.track_msid,
                     DENSE_RANK() OVER(PARTITION by year, week ORDER BY COUNT(lf.track_msid) DESC) as weekly_track_rank,
                     COUNT(lf.track_msid) AS weekly_track
            FROM    stream lf    INNER JOIN track tr ON lf.track_msid == tr.track_msid
                                        INNER JOIN time t   ON lf.timestamp_unix_id == t.timestamp_unix_id
                                        INNER JOIN artist a   ON lf.artist_msid == a.artist_msid
            GROUP BY  lf.track_msid, year, week
            ORDER BY  year, week, weekly_track DESC""", conn)

    top_track_prev_week_rank_table = sqldf("""
            SELECT    year, week, weekly_track_rank, weekly_track, track_name, artist_name,
                            LAG(weekly_track_rank) OVER (partition by track_msid ) AS rank_previous_week
            FROM      top_track_weekly_rank_table
            ORDER BY  year, week, weekly_track_rank""")

    return sqldf(f"""
            SELECT    year, week, weekly_track_rank, weekly_track, (track_name || ' ('|| artist_name|| ')') AS track_name ,
                      (rank_previous_week - weekly_track_rank) as weekly_change_of_ranking
            FROM      top_track_prev_week_rank_table
            WHERE     weekly_track_rank <= {top_n}
            ORDER BY  year DESC, week DESC, weekly_track_rank""")

def weekly_trend_artist(conn:sqlite3.Connection, top_n:str = "5") -> pd.DataFrame:
    """
    Queries the weekly top n ranked artists and the positoin changes to prev week.
    Parameters:
        conn (sqlite3.Connection): Databse connection
        top_n (str): Amount of top artists per week
    Returns:
        result (pd.DataFrame): dataframe with the query output
    """

    top_artist_weekly_rank_table = pd.read_sql_query("""
	        SELECT   year, week, artist_name, lf.artist_msid,
                     DENSE_RANK() OVER(PARTITION by year, week ORDER BY COUNT(lf.artist_msid) DESC) as weekly_artist_rank,
                     COUNT(lf.artist_msid) AS weekly_artist
            FROM     stream lf INNER JOIN artist a ON lf.artist_msid == a.artist_msid
                                INNER JOIN time t   ON lf.timestamp_unix_id == t.timestamp_unix_id

            GROUP BY lf.artist_msid, year, week
            ORDER BY year, week, weekly_artist DESC""", conn)

    top_artist_prev_week_rank_table = sqldf("""
            SELECT   year, week, weekly_artist_rank, weekly_artist, artist_name,
                     LAG(weekly_artist_rank) OVER (partition by artist_msid ) AS rank_previous_week
            FROM     top_artist_weekly_rank_table
            ORDER BY year, week, weekly_artist_rank""")

    return sqldf(f"""
            SELECT   year, week, weekly_artist_rank, weekly_artist, artist_name,
                     (rank_previous_week- weekly_artist_rank) as weekly_change_of_ranking
            FROM     top_artist_prev_week_rank_table
            where    weekly_artist_rank <= {top_n}
            ORDER BY year DESC, week DESC, weekly_artist_rank""")

def weekly_trend_release(conn:sqlite3.Connection, top_n:str = "5") -> pd.DataFrame:
    """
    Queries the weekly top n ranked release and the positoin changes to prev week.
    Parameters:
        conn (sqlite3.Connection): Databse connection
        top_n (str): Amount of top release per week
    Returns:
        result (pd.DataFrame): dataframe with the query output
    """
    top_release_rank_table = pd.read_sql_query("""
	        SELECT   year, week, release_name, artist_name, lf.release_msid,
                     DENSE_RANK() OVER(PARTITION by year, week ORDER BY COUNT(lf.release_msid) DESC) as weekly_release_rank,
                     COUNT(lf.release_msid) AS weekly_release
            FROM     stream lf    INNER JOIN release r ON lf.release_msid == r.release_msid
                                            INNER JOIN time t   ON lf.timestamp_unix_id == t.timestamp_unix_id
                                            INNER JOIN artist a   ON lf.artist_msid == a.artist_msid
            WHERE    lf.release_msid != "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
            GROUP BY lf.release_msid, year, week
            ORDER BY year, week, weekly_release DESC""", conn)

    top_release_prev_rank_table = sqldf("""
            SELECT    year, week, weekly_release_rank, weekly_release, release_name, artist_name,
                      LAG(weekly_release_rank) OVER (partition by release_msid ) AS rank_previous_week
            FROM      top_release_rank_table
            ORDER BY  year, week, weekly_release_rank""")

    return sqldf(f"""
            SELECT    year, week, weekly_release_rank, weekly_release, release_name, artist_name,
                      (rank_previous_week - weekly_release_rank) as weekly_change_of_ranking
            FROM      top_release_prev_rank_table
            where     weekly_release_rank <= {top_n}
            ORDER BY  year DESC, week DESC, weekly_release_rank""")

#################################################################################################
#                                                                                #
#                                         PLOTS                                                 #
#                                                                                #
#################################################################################################

def plot_active_users_over_weeks_and_weekdays_by_year(df:pd.DataFrame, year: str):
    """
    Plots a barchart on active users distributed over calendar weeks and weekdays based on a defined year.
    Parameters:
        df (pd.DataFrame): Dataframe from query_active_users_over_weeks_and_weekdays_by_year
        year (str): The year for which the plot is to be created.
    """
    DAY_ORDER = ['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday', 'Sunday']

    df['weekday'] = pd.Categorical(df['weekday'], categories=DAY_ORDER,ordered=True)
    df = df.pivot(index=["week"], columns="weekday",values="active_user")

    ax = df.plot(kind='bar',width=0.9, title='Active users distributed over calendar weeks and weekdays')
    ax.set_xlabel(f"Calendar Weeks {year}")
    ax.set_ylabel("Active User")
    plt.legend( title = "Weekdays")

def plot_streams_over_weekdays(df:pd.DataFrame) :
    """
    Plots the average streams over weekdays by year.
    Parameters:
        df (pd.DataFrame): Dataframe, provided by query_streams_over_weekdays
    """
    DAY_ORDER = ['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday', 'Sunday']

    df['weekday'] = pd.Categorical(df['weekday'], categories=DAY_ORDER,ordered=True)
    df = df.pivot(index="weekday",columns="year", values="avg_streams")

    ax = df.plot(kind='bar',width=0.8,rot=1, title='Average streams over weekdays by year')
    for bar in ax.patches:
        ax.annotate(format(bar.get_height(), '.0f'),
                       (bar.get_x() + bar.get_width() / 2,
                        bar.get_height()*0.9), ha='center', va='center',
                       xytext=(0, 8),textcoords='offset points')

    plt.xlabel('Weekdays')
    plt.ylabel('Average streams')
    plt.xticks(fontsize=8)
    plt.legend( title = "Year")

def plot_chart_table(df:pd.DataFrame,year:int,week:int, ax:plt.axes, subject:str ):
    """
    Combine weekly artist, release and song table plots into one figure.
    Parameters:
        conn (sqlite3.Connection): Databse connection.
        week (int): Week to get the charts
        year (int): Yearto get the charts
    """
    df = df.loc[(df['year']==year) & (df['week']==week)].copy()

    color_vals = df[[f"{subject.lower()}_name", 'weekly_change_of_ranking']].values
    colours = [['white' , 'blue' if row[1]!=row[1] else 'green' if float(row[1])>0 else 'red' if float(row[1])<0 else 'white' ] for row in color_vals]

    correct_rating = lambda x : " ".join((x, "△")) if (float(x) > 0 ) else " ".join((x, "▽")) if (float(x) < 0 ) else " ".join((x, "◁"))
    df["weekly_change_of_ranking"] = df["weekly_change_of_ranking"].astype(str)
    df["weekly_change_of_ranking"] = df["weekly_change_of_ranking"].apply(lambda x: x.replace('.0',''))
    df["weekly_change_of_ranking"] = df["weekly_change_of_ranking"].apply(correct_rating)
    vals = df[[f"{subject.lower()}_name", 'weekly_change_of_ranking']].values

    column_names = [f"{subject}", 'Changed position\ncompared to previous Week']

    ax.set_title(f"Spotify {subject} of the Week\nYear: {str(year)} Week: {str(week)}")
    the_table= ax.table(rowLabels=df[f"weekly_{subject.lower()}_rank"].values, colLabels=column_names,
                        colWidths = [0.25]*vals.shape[1], cellLoc='center',
                        loc='upper center', cellColours=colours, cellText=vals)
    the_table.set_fontsize(15)
    the_table.scale(1,1.5)

#################################################################################################
#                                                                                #
#                                         HELPER                                                #
#                                                                                #
#################################################################################################

def combine_chart_table_plots(conn:sqlite3.Connection, week:int, year:int):
    """
    Combine weekly artist, release and song table plots into one figure.
    Parameters:
        conn (sqlite3.Connection): Databse connection.
        week (int): Week to get the charts
        year (int): Yearto get the charts
    """
    fig, axes = plt.subplots(3, 1, figsize=(10,10))
    [axe.axis('off') for axe in axes]

    df= weekly_trend_artist(conn)
    plot_chart_table(ax=axes[0], subject="Artist", df=df, week=week, year=year)

    df= weekly_trend_track(conn)
    plot_chart_table(ax=axes[1], subject="Track", df=df, week=week, year=year)

    df= weekly_trend_release(conn)
    plot_chart_table(ax=axes[2], subject="Release", df=df, week=week, year=year)

def get_table_statistics(conn:sqlite3.Connection, table_name:str, columns:set) -> pd.DataFrame:
    """
    Combine (pandas) discribe with a null_value statistics based on a table and columns.
    Parameters:
        conn (sqlite3.Connection): Databse connection.
        table_name (str): Databse connection.
        columns (set): Databse connection.
    Returns:
        result (pd.DataFrame): Combination (pandas) discribe and null_value statistics.
    """
    table_null_values= query_null_entry_statistic_on_columns( conn,table=table_name, columns=columns)
    table_descr = query_entire_table(conn, table_name=table_name).describe(include='all')
    table_descr = table_descr[columns].transpose()
    table_descr = table_null_values.join(table_descr)
    table_descr['table'] = table_name
    return table_descr

def combine_table_statistics(conn:sqlite3.Connection) -> pd.DataFrame:
    """
    Combines statistic table data from track, release, time and stream into one df.
    Parameters:
        conn (sqlite3.Connection): Databse connection.
    Returns:
        result (pd.DataFrame): Combination of track, release, time and stream statistic df's.
    """
    #Track table
    track_columns = ['track_number','disc_number', 'track_duration_ms' ]
    track_statistics = get_table_statistics(conn,table_name='track',columns=track_columns)

    #Release table
    release_columns =['total_discs','total_tracks','release_date', 'release_name' ]
    release_statistics = get_table_statistics(conn,table_name='release',columns=release_columns)

    #time table
    time_columns =['day_of_week', 'day', 'week', 'month' , 'year']
    time_statistics = get_table_statistics(conn,table_name='time',columns=time_columns)

    #stream table
    stream_columns =['dedup_tag' ]
    stream_statistics = get_table_statistics(conn,table_name='stream',columns=stream_columns)

    return pd.concat([track_statistics, release_statistics, time_statistics, stream_statistics])

def main():
        conn= sqlite3.connect(DB_PATH)

        print("\n\n1. Overview total user, streams, artists, releases and tracks count")
        print(query_total_entries_per_table(conn))

        print("\n\n2. Statistic over table columns based on pandas describe extended with a custom Null-value percentage column")
        print(combine_table_statistics(conn))

        print("\n\n3. Displays active users, distributed over calendar weeks and weekdays for 2019 as barchart")
        print("See Figure1")
        year="2019"
        df = query_active_users_over_weeks_and_weekdays_by_year(conn,year=year)
        plot_active_users_over_weeks_and_weekdays_by_year(df, year=year)

        print("\n\n4. Displays the average streams over weekdays by year as barchart.")
        print("See Figure2")
        df = query_streams_over_weekdays(conn)
        plot_streams_over_weekdays(df)

        print("\n\n5. Displays weekly Spotify Release, Artist and Track Charts as plotted tables")
        print("See Figure3")
        combine_chart_table_plots(conn, week=12, year=2019)

        plt.show()
        conn.close()

if __name__ == '__main__':
    main()


# thank you DoTheEvo from stakcoverflow
# https://stackoverflow.com/questions/30175644/pyqt-listview-with-html-rich-text-delegate-moves-text-bit-out-of-placepic-and-c/35420113

from __future__ import annotations

from collections import defaultdict
from typing import TYPE_CHECKING

from qtpy.QtCore import QSize
from qtpy.QtGui import QAbstractTextDocumentLayout, QPainter, QPalette, QTextDocument
from qtpy.QtWidgets import (
    QApplication,
    QFileSystemModel,
    QStyle,
    QStyledItemDelegate,
    QStyleOptionViewItem,
)

if TYPE_CHECKING:
    from typing import Optional

    from qtpy.QtCore import QModelIndex
    from qtpy.QtWidgets import QWidget

class EntryDelegate(QStyledItemDelegate):
    def __init__(self, parent: Optional[QWidget] = None):
        super().__init__()
        self.doc = QTextDocument(self)

    def paint(
        self, painter: QPainter, option: QStyleOptionViewItem, index: QModelIndex
    ) -> None:
        painter.save()

        options = QStyleOptionViewItem(option)
        self.initStyleOption(options, index)

        if isinstance(index.model().sourceModel(), QFileSystemModel):
            fileSystemModel = index.model().sourceModel()
            fileSystemIndex = index.model().mapToSource(index)
            if fileSystemModel.isDir(fileSystemIndex):
                entry = defaultdict(bool)
            else:
                entry = index.model().currentSelection(index)
        else:
            entry = index.model().currentSelection(index)

        if entry is not None:
            skipText = "🤬" if entry["skip"] else " "
            flagText = "🚩" if entry["flag"] else " "
            notaText = "❓" if entry["nota"] else " "

            text = f"{notaText}{skipText}{flagText}{options.text}"
        else:
            text = f"{options.text}"
        self.doc.setHtml(text)

        options.text = ""

        style = (
            QApplication.style() if options.widget is None else options.widget.style()
        )
        style.drawControl(QStyle.CE_ItemViewItem, options, painter)

        ctx = QAbstractTextDocumentLayout.PaintContext()
        if option.state & QStyle.State_Selected:
            ctx.palette.setColor(
                QPalette.Text, option.palette.color(QPalette.Active, QPalette.Text)
            )

        textRect = style.subElementRect(QStyle.SE_ItemViewItemText, options)

        if index.column() != 0:
            textRect.adjust(5, 0, 0, 0)

        thefuckyourshitup_constant = 4
        margin = (option.rect.height() - options.fontMetrics.height()) // 2
        margin = margin - thefuckyourshitup_constant
        textRect.setTop(textRect.top() + margin)

        painter.translate(textRect.topLeft())
        painter.setClipRect(textRect.translated(-textRect.topLeft()))
        self.doc.documentLayout().draw(painter, ctx)

        painter.restore()

    def sizeHint(self, option: QStyleOptionViewItem, index: QModelIndex) -> QSize:
        return QSize(int(self.doc.idealWidth()), int(self.doc.size().height()))


import pathlib
import pysrpm.rpm

package_root = pathlib.Path(__file__).parent / 'setupcfg_package'

def get_rpm_instance(source=package_root, **options):
    return pysrpm.rpm.RPM(source, **options)

def get_metadata(with_deps=False, **options):
    with get_rpm_instance(**options) as rpm:
        rpm.load_configuration()
        return rpm.load_source_metadata(rpm.root, with_deps)

def get_specfile(tag=None, **options):
    with get_rpm_instance(**options) as rpm:
        rpm.load_configuration()
        pkg_info = rpm.load_source_metadata(rpm.root, False)

        spec = rpm.make_spec(pkg_info)
        if tag:
            return [line.split(':', 1)[1].strip() for line in spec.split('\n') if line.startswith(f'{tag}: ')]
        else:
            return spec

def test_template_specialisation():
    # Default
    assert get_specfile('Provides') == ['python%{python3_version}dist(package) = 0.0.0']

    # CLI / kwargs override
    assert get_specfile('Provides', templates=dict(python_dist='python-{name}')) == ['python-package = 0.0.0']

    # Try from a config file with an inherited flavour
    cfg = pathlib.Path('test.config')
    with open(cfg, 'w') as f:
        print('[pysrpm]\nflavour=test\n[test]\npython_dist=python-{name}', file=f)

    try:
        assert get_specfile('Provides', config=cfg) == ['python-package = 0.0.0']
    finally:
        cfg.unlink()

    # Add pysrpm. to config section
    with open(cfg, 'w') as f:
        print('[pysrpm]\nflavour=test\n[pysrpm.test]\npython_dist=python-{name}', file=f)

    try:
        assert get_specfile('Provides', config=cfg) == ['python-package = 0.0.0']
    finally:
        cfg.unlink()

    # Try from a TOML config file
    cfg = pathlib.Path('test.toml')
    with open(cfg, 'w') as f:
        print('''[tool.pysrpm]\nflavour = 'test'\n[tool.pysrpm.test]\npython_dist = "python-{name}"''', file=f)
    try:
        assert get_specfile('Provides', config=cfg) == ['python-package = 0.0.0']
    finally:
        cfg.unlink()


# Turtle Graphic Game
import time
import turtle
import math
import random
import os
import simpleaudio as sa
import wave

# Sounds
bounce_sound = sa.WaveObject.from_wave_file('Bounce.wav')
collision_sound = sa.WaveObject.from_wave_file('Collision.wav')

# Set up screen
wn = turtle.getscreen()
wn.clear()
wn.bgcolor('lightgreen')
#wn.bgpic('TGGame.gif')
wn.tracer(3)

# Draw border
mypen = turtle.Turtle()
mypen.penup()
mypen.setposition(-330, -330)
mypen.pendown()
mypen.pensize(3)
for side in range(4):
    mypen.forward(660)
    mypen.left(90)
mypen.hideturtle()

# Create player Turtle
player = turtle.Turtle()
player.color('blue')
player.shape('circle')
player.shapesize(2, 2, 1)
player.penup()  # delete trace
player.speed(0)

# Create multiple goals
max_goals = 25
goals = []
for count in range(max_goals):
    goals.append(turtle.Turtle())
    goals[count].color('dark red')
    goals[count].shape('circle')
    goals[count].penup()  # delete trace
    goals[count].speed(2)
    goals[count].setposition(random.randint(-300, 300), random.randint(-300, 300))

# Set speed variable
speed = 1

# Create the score of the game
game_score = 0

# Define functions
def turn_left():
    player.left(30)

def turn_right():
    player.right(30)

def increase_speed():
    global speed
    speed += 1

def decrease_speed():
    global speed
    speed -= 1

def is_collision(t1, t2):
    distance = math.sqrt(math.pow(t1.xcor() - t2.xcor(), 2) + math.pow(t1.ycor() - t2.ycor(), 2))
    if distance < 20:
        return True
    else:
        return False

# Set keyboard binding
turtle.listen()
turtle.onkey(turn_left, "Left")
turtle.onkey(turn_right, "Right")
turtle.onkey(increase_speed, "Up")
turtle.onkey(decrease_speed, "Down")

while True:
    player.forward(speed)

    # Boundary Checking for player
    if player.xcor() > 330 or player.xcor() < -330:
        player.right(random.randint(160, 200))
        play_bounce = bounce_sound.play()
        play_bounce.stop()

    if player.ycor() > 330 or player.ycor() < -330:
        player.right(random.randint(160, 200))
        play_bounce = bounce_sound.play()
        play_bounce.stop()

    # Move the goal
    for count in range(max_goals):
        goals[count].forward(0.3)

        # Collision checking
        if is_collision(player, goals[count]):
            goals[count].setposition(random.randint(-300, 300), random.randint(-300, 300))
            goals[count].right(random.randint(0, 360))
            game_score += 1
            # Draw  the score on the screen
            mypen.undo()
            mypen.penup()
            mypen.hideturtle()
            mypen.setposition (-300, 330)
            score_string = 'Score: %s' %game_score
            mypen.write(score_string, False, align='left', font=('Arial', 14, 'normal'))
            play_collision = collision_sound.play()
            play_collision.stop()

        # Boundary Checking for goal
        if goals[count].xcor() > 320 or goals[count].xcor() < -320:
            goals[count].right(random.randint(110, 250))

        if goals[count].ycor() > 320 or goals[count].ycor() < -320:
            goals[count].right(random.randint(110, 250))


# -*- coding: utf-8 -*-
"""
Created on Wed Dec 16 10:11:35 2020

@author: Zeno
"""
import numpy as np
import matplotlib.pyplot as plt
from sklearn.ensemble import RandomForestRegressor
"""
In our sample, there is a theatre and y is price of the seat
x is location of the seat.When seat's location gets closer to the scene,
price of the seat increases.We want to predict seat price.
"""
x=np.array([10,9,8,7,6,5,4,3,2,1]).reshape(-1,1) #Location of seats
y=np.array([5,10,16,23,32,41,50,61,75,100]) #Prices of seats
#Let's create Random forest model and fit x,y
random_forest_model = RandomForestRegressor(n_estimators=100,random_state=(42)).fit(x,y)
#as you can see, estimators numbers are 100
#and we don't want to get different predictions every time and for this our random state is 42

#Let's look at the prediction for seat's location is 7
print(random_forest_model.predict([[7]])) #prediction=24.6 <-> real price=23

x_gradually = np.arange(min(x),max(x),0.01).reshape(-1,1) #we want to predict and visualize gradually
y_head = random_forest_model.predict(x_gradually)#prediction variable
#visualization
plt.scatter(x,y,color = "black")
plt.plot(x_gradually,y_head,color ="purple",label ="Random Forest Regression")
plt.xlabel("location of the seat")
plt.ylabel("price of the seat")
plt.grid(True)
plt.legend()
plt.show()


import matplotlib.pyplot as plt
import pickle
import numpy as np
import random
import pandas as pd

def plot_percentiles(x):
    plt.figure(figsize=(14,7))
    plt.style.use('seaborn-whitegrid')
    plt.hist(x,bins=30,density=True, facecolor = '#2ab0ff', edgecolor='#169acf', linewidth=0.5)
    plt.xlim(0,100)
    plt.xlabel('Predicted action percentile')
    plt.ylabel('Density of datasets')
    plt.show()

def percentage_greater_than(lst, x):
    count_greater_than_x = sum(1 for item in lst if item < x)
    percentage = (count_greater_than_x / len(lst)) * 100

    return percentage

with open('oldmodel/10x10percentiles.pkl', 'rb') as file:
    scores = pickle.load(file)

#scores = [i-2 if i<10 else i-5 for i in scores]
#scores = [i-2 for i in scores]
#scores = [i-17 if i>15 else i-1 for i in scores]
#scores = [i-38 if i>20 and random.random()<0.9 else i+1 for i in scores]
#scores = [i-22 if i>30 else i for i in scores]
#print(percentage_greater_than(scores, 30))
#plot_percentiles(scores)

data = [-4/100, -3/100, -1/100, 0/100, 1/100]

df = pd.DataFrame(data, columns=['Pairwise'])

dataold = [-30/100, -14/100, -10/100, -9/100, -3/100]

# Appending the new data to the DataFrame
df['Conv2d'] = dataold

# Existing data
data1 = [-26/100, -15/100, -13/100, -10/100, -2/100]

# Appending the new data to the DataFrame
df['RLAlign'] = data1

# Creating box plot with whiskers at min and max for both sets of data
plt.figure(figsize=(6, 4))
df.boxplot(column=['Pairwise', 'Conv2d'], whis=[0, 100],patch_artist=False, widths=0.5)
plt.xlabel('Model')
plt.ylabel('Alignment score difference')
plt.show()


import json
from fastapi import APIRouter, Request, BackgroundTasks
from fastapi_versioning import version
from configurations.properties import API_VERSION
from services.game_service import GameParser
from services.storage_client import cache_input, check_input_cache

router = APIRouter()

@router.get("/api/games/{game_title}")
@version(API_VERSION)
def get_game_info(game_title: str, request: Request, background_task: BackgroundTasks):
    raw_request: str = str(request.url)
    cached_report = check_input_cache(raw_request)

    if cached_report:
        report: dict = json.loads(cached_report)
    else:
        game_parser = GameParser()

        if len(request.query_params) > 0:
            report = game_parser.get_content_info(game_title, request)
        else:
            report = game_parser.parse_game_info(game_title)

    # Cache client request
    background_task.add_task(cache_input, raw_request, json.dumps(report))

    return {
        game_title: report
    }


#!flask/bin/python
from flask import Flask, request, request_started
import os
app = Flask(__name__)
counter = 0
if os.path.isfile('counter.txt'):
   print("file already exists")
   f = open("counter.txt", "r")
   for i in f:
       if i.idigit() == True:
           counter = int(i)
else:
    os.system('touch counter.txt')
@app.route('/', methods=["POST", "GET"])
def index():
    global counter
    if request.method == "POST":
        counter+=1
        g = open('counter.txt', 'rw+')
        g.truncate(0)
        g.write(str(counter))
        return "Hmm, Plus 1 please "
    else:
        return str("Our Counter Is= " + str(counter))
if __name__ == '__main__':
    app.run(debug=True,port=80,host='0.0.0.0')


""" Contains URL paths for the restaurant bookings app,
forming part of the main Sushi & Sake website """
from django.urls import path
from . import views

urlpatterns = [
    path('book/', views.BookingView.as_view(), name='BookTable'),
    path('mybookings/', views.UserBookings.as_view(), name='mybookings'),
    path('editbooking/<booking_id>/', views.EditBooking.as_view(),
         name='editbooking'),
    path('confirmdelete/<booking_id>/', views.ConfirmDelete.as_view(),
         name='confirmdelete'),
]


# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
# vi: set ft=python sts=4 ts=4 sw=4 et:
import nose
import tempfile

try:
    import matplotlib as mp
    # Make really sure that we don't try to open an Xserver connection.
    mp.use('template', warn=False)
    import pylab as pl
    pl.switch_backend('template')
except ImportError:
    raise nose.SkipTest('Could not import matplotlib')

from nilearn.plotting.displays import OrthoSlicer, XSlicer, OrthoProjector
from nilearn.datasets import load_mni152_template

###############################################################################
# Some smoke testing for graphics-related code

def test_demo_ortho_slicer():
    # This is only a smoke test
    mp.use('template', warn=False)
    import pylab as pl
    pl.switch_backend('template')
    pl.clf()
    oslicer = OrthoSlicer(cut_coords=(0, 0, 0))
    img = load_mni152_template()
    oslicer.add_overlay(img, cmap=pl.cm.gray)
    oslicer.close()

def test_stacked_slicer():
    # Test stacked slicers, like the XSlicer
    mp.use('template', warn=False)
    import pylab as pl
    pl.switch_backend('template')
    pl.clf()
    img = load_mni152_template()
    slicer = XSlicer.init_with_figure(img=img, cut_coords=3)
    slicer.add_overlay(img, cmap=pl.cm.gray)
    # Forcing a layout here, to test the locator code
    slicer.savefig(tempfile.TemporaryFile())
    slicer.close()

def test_demo_ortho_projector():
    # This is only a smoke test
    mp.use('template', warn=False)
    import pylab as pl
    pl.switch_backend('template')
    pl.clf()
    img = load_mni152_template()
    oprojector = OrthoProjector.init_with_figure(img=img)
    oprojector.add_overlay(img, cmap=pl.cm.gray)
    oprojector.savefig(tempfile.TemporaryFile())
    oprojector.close()


# Number Guessing Game

import random
from art import logo
from os import name,system

def clear():
    if name == 'nt':
        system('cls')
    else:
        system('clear')

def play_the_game(random_number, attempts):
    flag = True
    print(f"You have {attempts} attempts to guess the number")
    while attempts > 0 and flag:
        num = int(input("Make a Guess "))
        if num > random_number:
            print("Too High")
        elif num < random_number:
            print("Too Low")
        else:
            print(f"You've Got it , Generated number was {random_number}")
            flag = False
        attempts -= 1
    if flag:
        print(f"Sorry you've ran out of the attempts , Generated number was {random_number}")

play = True
while play:
    print(logo)
    game_level = input("Welcome to the number guessing game :)\nI'm thinking of "
                       "a number between 1 and 100\nchoose a difficulty. Type 'easy' or 'hard': ").lower()
    if game_level in ['easy', 'hard']:
        number = random.randint(1, 100)
        if game_level == 'easy':
            no_of_attempts = 10
        else:
            no_of_attempts = 5
        play_the_game(number, no_of_attempts)
        choice = input("Do you still want to continue the game 'y' or 'n' ").lower()
        if choice != 'y':
            play = False
        clear()

    else:
        print("Please select a valid level (easy / hard )")
        clear()


import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
import pandas as pd

import calendar
from datetime import date

from scipy import stats
import collections

import os
import re # reg ex

def main():
    #makeFiles()
    #clean_csv('www.google.com', day=['2018-03-25', '2018-03-24'])
    create_plot_temporal_v(site = "aggregate", fshow_hours = False, fshow_daily = True, savefig = True)
    #create_plot_cdf(site = "aggregate", showfig = True)
    #create_plot_all(savefig = True)
    #create_plot_all(showfig = True)
    #create_plot_cdf(showfig = True, day = "2018-02-11 Sunday")
    #create_plot_violin(site = "aggregate")
    #create_plot_violin("www.taobao.com")
    #create_plot_cdf(showfig = True, savefig = True)
    #create_plot_cdf(showfig = True, day = "2018-02-13 Tuesday")
    #create_plot_cdf(site = "www.google.com", showfig = True)
    #create_plot_cdf(site = "www.google.com", showfig = True)

def clean_csv(site = "aggregate", day = ['all'], show_hours = False, show_daily = False):
# get the important variables into the dataFrame. For specific site or aggregate,
# and also return only specific days in the dataframe if specified other than 'all'
    df = pd.read_csv("aggregate.txt")
    df = df.loc[:, ['tstamp', 'sitename', 'RTT', 'RTTtwo', 'RTTthree', 'pingRTT']]
    df = df if site == "aggregate" else df[df.sitename.str.match('^' + site + '$')] #regex for getting the exact site if specified
    #df = df if day == ['all'] else df[df.tstamp.str.contains('|'.join([x for x in day])) if len(day) > 1 else df.tstamp.str.contains(day[0])] #this line isolates specific days as specified by the array day otherwise it shows all the days if default value of ['all'] is used for days
    df = df[~df.pingRTT.str.match("None")]
    df.pingRTT.apply(lambda val: float(val))
    df.pingRTT = df.pingRTT.astype('float64')
    if show_hours:
        df.tstamp = df.tstamp.apply(lambda val: convert_tstamp_to_hour(val))
        print(df)
    if show_daily:
        df.tstamp = df.tstamp.apply(lambda val: convert_tstamp_to_day_of_week(val))
        print(df)
    return df

def convert_tstamp_to_hour(tstamp):
    '''
    used in the clean_csv function to change a noisy tstamp
    to just the hour, rounded down
    '''
    p = re.compile('(2018-03-\d\d) ((\d\d).*)') # group 3 contains the hour
    m = p.match(tstamp)
    hour = m.group(3)
    return hour

def convert_tstamp_to_day_of_week(tstamp):
    p = re.compile('(2018)-(03)-(\d\d)')
    m = p.match(tstamp)
    year = int(m.group(1))
    month = int(m.group(2))
    day = int(m.group(3))
    number = date.weekday(date(year, month, day))
    return calendar.day_name[number]

def makeFiles(file = "aggregate.txt"):
#the purpose of this function is to create
#a separate log file for each website in the
#directory folder logs.
    wd = os.getcwd() # this is where to get the files
    data = clean_csv() # get the values from the aggregate
    sites = data.sitename.unique() # get the unique sitenames
    for site in sites: # for each site
        f = open(wd + '/logs/' + site, 'w') # create new file with sitename in logs
        f.write(data[data.sitename.str.match('^' + site + '$')].to_csv()) # and write site specific data

def create_plot_all(savefig = False, showfig = False, day = ['all']):
    wd = os.getcwd() + '/logs/' # this is where to get the files
    sites = os.listdir(wd) # the list of sites in the directory
    create_plot_cdf(site = "aggregate", showfig = showfig, savefig = savefig)
    for site in sites:
        f = open(wd + site) # open individual site
        create_plot_cdf(site, showfig, savefig)

def create_plot_cdf(site, showfig = False, savefig = False, day = ['all'], show_hours = False):
    data = clean_csv(site, show_hours)
    data = data.loc[:, ['RTT', 'RTTtwo', 'RTTthree', 'pingRTT']] # only want the relevant columns in dataframe
    y = np.arange(1, len(data.RTT)+1) / len(data.RTT)
    for col in data.columns:
        _ = plt.plot(np.sort(data[col]), np.linspace(0, 1, len(data[col]), endpoint=False))
    _ = plt.ylabel('ECDF')
    _ = plt.legend(['ping RTT', 'TTFB - PRET', 'TTFB - PRET 2ND LOAD', 'TTFB - PRET 3RD LOAD'])
    ax1 = plt.subplot(111)
    total = [data[x] for x in data]
    xlim_max = np.percentile(total, 97.5)
    ax1.set_xlim([0, xlim_max])
    plt.savefig('graphs/CDF-of-' + site.replace('www.','') + '.png') if savefig else {}
    plt.show() if showfig else {plt.close()}

def create_plot_temporal(site, showfig = True, savefig = False, day = ['all'], show_hours = True):
    data = clean_csv(site, show_hours = True)
    #data = dict(data)
    #print(data)
    for key, value in data.iteritems():
        df = data[key]
        _ = plt.plot(np.sort(df['RTTtwo']), np.linspace(0, 1, len(df['RTTtwo']), endpoint=False))
    _ = plt.ylabel('ECDF')
    _ = plt.legend([key for key, value in data.iteritems()])
    ax1 = plt.subplot(111)
    ax1.set_xlim([0, 1000])
    plt.show() if showfig else {plt.close()}

def create_plot_temporal_v(site = "aggregate", showfig = True, savefig = False, day = ['all'], fshow_hours = False, fshow_daily = False):
    dataf = clean_csv(site, show_hours = fshow_hours, show_daily = fshow_daily)
    sns.set_style("whitegrid")
    yposlist = dataf.groupby(['tstamp'])['RTTtwo'].median().tolist()
    # print(yposlist)
    # print(dataf.groupby(['tstamp'])['RTTtwo'])
    xposlist = range(len(yposlist))
    grouped = dataf.groupby('tstamp')
    print(np.std(dataf.RTTtwo, ddof=0))
    print(np.mean(dataf.RTTtwo))
    # print(grouped['RTTtwo'].agg('std'))
    stringlist = ["std:" + str(x) for x in grouped['RTTtwo'].agg(np.std)]
    ax1 = plt.subplot(111)
    ax1 = sns.violinplot(x = 'tstamp', y = 'RTTtwo', data = dataf)
    for i in range(len(stringlist)):
        ax1.text(xposlist[i], yposlist[i], stringlist[i])
    ax1.set_ylim([0, 750])
    plt.savefig('graphs/VIOLIN-of-weekly' + site.replace('www.','') + '.png', dpi = 600) if savefig else {}
    plt.show() if showfig else {plt.close()}

def create_plot_violin(site = "aggregate", day = ""):
    data = clean_csv(site)
    data = data.loc[:, ['RTT', 'RTTtwo', 'RTTthree', 'pingRTT']]
    sns.set_style("whitegrid")
    sns.violinplot(data=data)
    ax1 = plt.subplot()
    total = [data[x] for x in data]
    ylim_max = np.percentile(total, 97.5)
    ax1.set_ylim([0, ylim_max])
    plt.show()

main()


from typing import List, Dict, Callable
from Sounds.sounds import espeak
from MPD.MpdBase import Mpd
from models import ItemFile, session
from Sounds.sounds import load_beep
from config import (KEY_X,
                    KEY_B,
                    KEY_Y,
                    KEY_MENU,
                    GAME_A)
import logging
from Queue.QueueBase import QueueBase

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
formatter = logging.Formatter('%(levelname)s:%(name)s:%(funcName)s():%(message)s')

console_handler = logging.StreamHandler()
console_handler.setFormatter(formatter)

file_handler = logging.FileHandler("item_queue.log")
file_handler.setFormatter(formatter)

logger.addHandler(file_handler)
logger.addHandler(console_handler)

class ItemQueue(Mpd, QueueBase, object):

    """Extends core mpd functions for the Item queue.
    """

    def __init__(self):
        """
        :current_queue: The name of the current queue.

        :active_keys: Methods available in current queue.

        :clozing: Boolean. True if clozing.

        :recording: True if recording.

        :item_keys: Methods available in the item queue.

        :load_initial_queue: The initial queue method for this queue.
        """

        super().__init__()

        # State
        self.current_queue: str = "global item queue"
        self.active_keys: Dict[int, Callable] = {}
        self.clozing: bool = False
        self.recording: bool = False

        # Set initial queue method.
        self.load_initial_queue = self.get_global_items

        # Keys
        self.item_keys: Dict[int, Callable] = {
                KEY_X:      self.toggle,
                KEY_B:      self.previous,
                KEY_Y:      self.next,
                GAME_A:     self.archive_item
        }

    def get_global_items(self) -> bool:
        """Get global items and load the global item queue.

        :returns: True on success else false.
        """
        # Query DB for outstanding items
        items: List[ItemFile] = (session
                                 .query(ItemFile)
                                 .filter_by(deleted=False)
                                 .filter(ItemFile.question_filepath != None)
                                 .all())

        # Add mpd-recognised items to the queue
        if items:
            # List of rel_fps
            item_queue: List[str] = []
            for item in items:
                rel_fp = self.abs_to_rel(item.question_filepath)
                if self.mpd_recognised(rel_fp):
                    item_queue.append(rel_fp)
            if item_queue:
                queue_length = self.load_queue(item_queue)
                if queue_length > 0:
                    logger.info(f"Loaded a global item queue with {queue_length} items.")
                    espeak(f"{queue_length} global item{'' if queue_length == 1 else 's'}")
                    self.load_global_item_options()
                    return True
                else:
                    logger.error("Call to load_queue failed.")
            else:
                logger.info("No MPD-recognised items found in DB.")
        else:
            logger.info("No items found in DB.")
        return False

    def load_global_item_options(self) -> None:
        """Set state options for global item queue.
        """
        # Set playback options
        self.repeat(1)
        self.single(1)
        # Set state information options
        self.active_keys = self.item_keys
        self.current_queue = "global item queue"
        self.clozing = False
        self.recording = False
        logger.info("Loaded global item options.")

    def archive_item(self) -> bool:
        """Archive the current item.

        Archived items will be deleted by a script.
        Non-archived items are archived and deleted after export
        """
        assert self.current_queue in ["local item queue",
                                      "global item queue"]

        # Get the currently playing item
        cur_song = self.current_track()
        filepath = cur_song['abs_fp']

        if filepath:
            # Find the item in DB
            item: ItemFile = (session
                              .query(ItemFile)
                              .filter_by(question_filepath=filepath)
                              .one_or_none())

            # Archive the item
            if item:
                item.archived = True
                session.commit()
                load_beep()
                logger.info("Archived an item.")
                return True
            else:
                logger.error("Currently playing item not found in DB.")
        else:
            logger.error("No currently playing track.")
        return False