<commit_before>import sys, json, uuid, os, time, datetime, re
from misp2cybox import *
from dateutil.tz import tzutc
from stix.indicator import Indicator
from stix.indicator.valid_time import ValidTime
from stix.ttp import TTP, Behavior
from stix.ttp.malware_instance import MalwareInstance
from stix.incident import Incident, Time, ImpactAssessment, ExternalID, AffectedAsset
from stix.exploit_target import ExploitTarget, Vulnerability
from stix.incident.history import JournalEntry, History, HistoryItem
from stix.threat_actor import ThreatActor
from stix.core import STIXPackage, STIXHeader
from stix.common import InformationSource, Identity, Confidence
from stix.data_marking import Marking, MarkingSpecification
from stix.extensions.marking.tlp import TLPMarkingStructure
from stix.common.related import *
from stix.common.confidence import Confidence
from stix.common.vocabs import IncidentStatus
from cybox.utils import Namespace

NS_DICT = {
        "http://cybox.mitre.org/common-2" : 'cyboxCommon',
        "http://cybox.mitre.org/cybox-2" : 'cybox',
        "http://cybox.mitre.org/default_vocabularies-2" : 'cyboxVocabs',
        "http://cybox.mitre.org/objects#ASObject-1" : 'ASObj',
        "http://cybox.mitre.org/objects#AddressObject-2" : 'AddressObj',
        "http://cybox.mitre.org/objects#DomainNameObject-1" : 'DomainNameObj',
        "http://cybox.mitre.org/objects#EmailMessageObject-2" : 'EmailMessageObj',
        "http://cybox.mitre.org/objects#FileObject-2" : 'FileObj',
        "http://cybox.mitre.org/objects#HTTPSessionObject-2" : 'HTTPSessionObj',
        "http://cybox.mitre.org/objects#HostnameObject-1" : 'HostnameObj',
        "http://cybox.mitre.org/objects#MutexObject-2" : 'MutexObj',
        "http://cybox.mitre.org/objects#PipeObject-2" : 'PipeObj',
        "http://cybox.mitre.org/objects#URIObject-2" : 'URIObj',
        "http://cybox.mitre.org/objects#WinRegistryKeyObject-2" : 'WinRegistryKeyObj',
        "http://data-marking.mitre.org/Marking-1" : 'marking',
        "http://data-marking.mitre.org/extensions/MarkingStructure#TLP-1" : 'tlpMarking',
        "http://stix.mitre.org/ExploitTarget-1" : 'et',
        "http://stix.mitre.org/Incident-1" : 'incident',
        "http://stix.mitre.org/Indicator-2" : 'indicator',
        "http://stix.mitre.org/TTP-1" : 'ttp',
        "http://stix.mitre.org/ThreatActor-1" : 'ta',
        "http://stix.mitre.org/common-1" : 'stixCommon',
        "http://stix.mitre.org/default_vocabularies-1" : 'stixVocabs',
        "http://stix.mitre.org/extensions/Identity#CIQIdentity3.0-1" : 'stix-ciqidentity',
        "http://stix.mitre.org/extensions/TestMechanism#Snort-1" : 'snortTM',
        "http://stix.mitre.org/stix-1" : 'stix',
        "http://www.w3.org/2001/XMLSchema-instance" : 'xsi',
        "urn:oasis:names:tc:ciq:xal:3" : 'xal',
        "urn:oasis:names:tc:ciq:xnl:3" : 'xnl',
        "urn:oasis:names:tc:ciq:xpil:3" : 'xpil',
}

SCHEMALOC_DICT = {
	'http://cybox.mitre.org/common-2': 'http://cybox.mitre.org/XMLSchema/common/2.1/cybox_common.xsd',
	'http://cybox.mitre.org/cybox-2': 'http://cybox.mitre.org/XMLSchema/core/2.1/cybox_core.xsd',
	'http://cybox.mitre.org/default_vocabularies-2': 'http://cybox.mitre.org/XMLSchema/default_vocabularies/2.1/cybox_default_vocabularies.xsd',
	'http://cybox.mitre.org/objects#ASObject-1': 'http://cybox.mitre.org/XMLSchema/objects/AS/1.0/AS_Object.xsd',
	'http://cybox.mitre.org/objects#AddressObject-2': 'http://cybox.mitre.org/XMLSchema/objects/Address/2.1/Address_Object.xsd',
	'http://cybox.mitre.org/objects#DomainNameObject-1': 'http://cybox.mitre.org/XMLSchema/objects/Domain_Name/1.0/Domain_Name_Object.xsd',
	'http://cybox.mitre.org/objects#EmailMessageObject-2': 'http://cybox.mitre.org/XMLSchema/objects/Email_Message/2.1/Email_Message_Object.xsd',
	'http://cybox.mitre.org/objects#FileObject-2': 'http://cybox.mitre.org/XMLSchema/objects/File/2.1/File_Object.xsd',
	'http://cybox.mitre.org/objects#HTTPSessionObject-2': 'http://cybox.mitre.org/XMLSchema/objects/HTTP_Session/2.1/HTTP_Session_Object.xsd',
	'http://cybox.mitre.org/objects#HostnameObject-1': 'http://cybox.mitre.org/XMLSchema/objects/Hostname/1.0/Hostname_Object.xsd',
	'http://cybox.mitre.org/objects#MutexObject-2': 'http://cybox.mitre.org/XMLSchema/objects/Mutex/2.1/Mutex_Object.xsd',
	'http://cybox.mitre.org/objects#PipeObject-2': 'http://cybox.mitre.org/XMLSchema/objects/Pipe/2.1/Pipe_Object.xsd',
	'http://cybox.mitre.org/objects#URIObject-2': 'http://cybox.mitre.org/XMLSchema/objects/URI/2.1/URI_Object.xsd',
	'http://cybox.mitre.org/objects#WinRegistryKeyObject-2': 'http://cybox.mitre.org/XMLSchema/objects/Win_Registry_Key/2.1/Win_Registry_Key_Object.xsd',
	'http://data-marking.mitre.org/Marking-1': 'http://stix.mitre.org/XMLSchema/data_marking/1.1.1/data_marking.xsd',
	'http://data-marking.mitre.org/extensions/MarkingStructure#TLP-1': 'http://stix.mitre.org/XMLSchema/extensions/marking/tlp/1.1.1/tlp_marking.xsd',
	'http://stix.mitre.org/ExploitTarget-1': 'http://stix.mitre.org/XMLSchema/exploit_target/1.1.1/exploit_target.xsd',
	'http://stix.mitre.org/Incident-1': 'http://stix.mitre.org/XMLSchema/incident/1.1.1/incident.xsd',
	'http://stix.mitre.org/Indicator-2': 'http://stix.mitre.org/XMLSchema/indicator/2.1.1/indicator.xsd',
	'http://stix.mitre.org/TTP-1': 'http://stix.mitre.org/XMLSchema/ttp/1.1.1/ttp.xsd',
	'http://stix.mitre.org/ThreatActor-1': 'http://stix.mitre.org/XMLSchema/threat_actor/1.1.1/threat_actor.xsd',
	'http://stix.mitre.org/common-1': 'http://stix.mitre.org/XMLSchema/common/1.1.1/stix_common.xsd',
	'http://stix.mitre.org/default_vocabularies-1': 'http://stix.mitre.org/XMLSchema/default_vocabularies/1.1.1/stix_default_vocabularies.xsd',
	'http://stix.mitre.org/extensions/Identity#CIQIdentity3.0-1': 'http://stix.mitre.org/XMLSchema/extensions/identity/ciq_3.0/1.1.1/ciq_3.0_identity.xsd',
	'http://stix.mitre.org/extensions/TestMechanism#Snort-1': 'http://stix.mitre.org/XMLSchema/extensions/test_mechanism/snort/1.1.1/snort_test_mechanism.xsd',
	'http://stix.mitre.org/stix-1': 'http://stix.mitre.org/XMLSchema/core/1.1.1/stix_core.xsd',
	'urn:oasis:names:tc:ciq:xal:3': 'http://stix.mitre.org/XMLSchema/external/oasis_ciq_3.0/xAL.xsd',
	'urn:oasis:names:tc:ciq:xnl:3': 'http://stix.mitre.org/XMLSchema/external/oasis_ciq_3.0/xNL.xsd',
	'urn:oasis:names:tc:ciq:xpil:3': 'http://stix.mitre.org/XMLSchema/external/oasis_ciq_3.0/xPIL.xsd',
}

def main(args):
    if len(sys.argv) < 4:
        sys.exit("Invalid parameters")
    namespace = [sys.argv[1], sys.argv[2]]
    NS_DICT[namespace[0]]=namespace[1]
    stix.utils.idgen.set_id_namespace({namespace[0]: namespace[1]})
    stix_package = STIXPackage()
    stix_header = STIXHeader()
    stix_header.title="Export from " + namespace[1] + " MISP"
    stix_header.package_intents="Threat Report"
    stix_package.stix_header = stix_header
    if sys.argv[3] == 'json':
        stix_string = stix_package.to_json()[:-1]
        stix_string += ', "related_packages": ['
    else:
        stix_string = stix_package.to_xml(auto_namespace=False, ns_dict=NS_DICT, schemaloc_dict=SCHEMALOC_DICT)
        stix_string = stix_string.replace("</stix:STIX_Package>\n", "");
    print(stix_string)

if __name__ == "__main__":
    main(sys.argv)
<commit_msg>Correct CIQ namespace<commit_after>import sys, json, uuid, os, time, datetime, re
from misp2cybox import *
from dateutil.tz import tzutc
from stix.indicator import Indicator
from stix.indicator.valid_time import ValidTime
from stix.ttp import TTP, Behavior
from stix.ttp.malware_instance import MalwareInstance
from stix.incident import Incident, Time, ImpactAssessment, ExternalID, AffectedAsset
from stix.exploit_target import ExploitTarget, Vulnerability
from stix.incident.history import JournalEntry, History, HistoryItem
from stix.threat_actor import ThreatActor
from stix.core import STIXPackage, STIXHeader
from stix.common import InformationSource, Identity, Confidence
from stix.data_marking import Marking, MarkingSpecification
from stix.extensions.marking.tlp import TLPMarkingStructure
from stix.common.related import *
from stix.common.confidence import Confidence
from stix.common.vocabs import IncidentStatus
from cybox.utils import Namespace

NS_DICT = {
        "http://cybox.mitre.org/common-2" : 'cyboxCommon',
        "http://cybox.mitre.org/cybox-2" : 'cybox',
        "http://cybox.mitre.org/default_vocabularies-2" : 'cyboxVocabs',
        "http://cybox.mitre.org/objects#ASObject-1" : 'ASObj',
        "http://cybox.mitre.org/objects#AddressObject-2" : 'AddressObj',
        "http://cybox.mitre.org/objects#DomainNameObject-1" : 'DomainNameObj',
        "http://cybox.mitre.org/objects#EmailMessageObject-2" : 'EmailMessageObj',
        "http://cybox.mitre.org/objects#FileObject-2" : 'FileObj',
        "http://cybox.mitre.org/objects#HTTPSessionObject-2" : 'HTTPSessionObj',
        "http://cybox.mitre.org/objects#HostnameObject-1" : 'HostnameObj',
        "http://cybox.mitre.org/objects#MutexObject-2" : 'MutexObj',
        "http://cybox.mitre.org/objects#PipeObject-2" : 'PipeObj',
        "http://cybox.mitre.org/objects#URIObject-2" : 'URIObj',
        "http://cybox.mitre.org/objects#WinRegistryKeyObject-2" : 'WinRegistryKeyObj',
        "http://data-marking.mitre.org/Marking-1" : 'marking',
        "http://data-marking.mitre.org/extensions/MarkingStructure#TLP-1" : 'tlpMarking',
        "http://stix.mitre.org/ExploitTarget-1" : 'et',
        "http://stix.mitre.org/Incident-1" : 'incident',
        "http://stix.mitre.org/Indicator-2" : 'indicator',
        "http://stix.mitre.org/TTP-1" : 'ttp',
        "http://stix.mitre.org/ThreatActor-1" : 'ta',
        "http://stix.mitre.org/common-1" : 'stixCommon',
        "http://stix.mitre.org/default_vocabularies-1" : 'stixVocabs',
        "http://stix.mitre.org/extensions/Identity#CIQIdentity3.0-1" : 'ciqIdentity',
        "http://stix.mitre.org/extensions/TestMechanism#Snort-1" : 'snortTM',
        "http://stix.mitre.org/stix-1" : 'stix',
        "http://www.w3.org/2001/XMLSchema-instance" : 'xsi',
        "urn:oasis:names:tc:ciq:xal:3" : 'xal',
        "urn:oasis:names:tc:ciq:xnl:3" : 'xnl',
        "urn:oasis:names:tc:ciq:xpil:3" : 'xpil',
}

SCHEMALOC_DICT = {
	'http://cybox.mitre.org/common-2': 'http://cybox.mitre.org/XMLSchema/common/2.1/cybox_common.xsd',
	'http://cybox.mitre.org/cybox-2': 'http://cybox.mitre.org/XMLSchema/core/2.1/cybox_core.xsd',
	'http://cybox.mitre.org/default_vocabularies-2': 'http://cybox.mitre.org/XMLSchema/default_vocabularies/2.1/cybox_default_vocabularies.xsd',
	'http://cybox.mitre.org/objects#ASObject-1': 'http://cybox.mitre.org/XMLSchema/objects/AS/1.0/AS_Object.xsd',
	'http://cybox.mitre.org/objects#AddressObject-2': 'http://cybox.mitre.org/XMLSchema/objects/Address/2.1/Address_Object.xsd',
	'http://cybox.mitre.org/objects#DomainNameObject-1': 'http://cybox.mitre.org/XMLSchema/objects/Domain_Name/1.0/Domain_Name_Object.xsd',
	'http://cybox.mitre.org/objects#EmailMessageObject-2': 'http://cybox.mitre.org/XMLSchema/objects/Email_Message/2.1/Email_Message_Object.xsd',
	'http://cybox.mitre.org/objects#FileObject-2': 'http://cybox.mitre.org/XMLSchema/objects/File/2.1/File_Object.xsd',
	'http://cybox.mitre.org/objects#HTTPSessionObject-2': 'http://cybox.mitre.org/XMLSchema/objects/HTTP_Session/2.1/HTTP_Session_Object.xsd',
	'http://cybox.mitre.org/objects#HostnameObject-1': 'http://cybox.mitre.org/XMLSchema/objects/Hostname/1.0/Hostname_Object.xsd',
	'http://cybox.mitre.org/objects#MutexObject-2': 'http://cybox.mitre.org/XMLSchema/objects/Mutex/2.1/Mutex_Object.xsd',
	'http://cybox.mitre.org/objects#PipeObject-2': 'http://cybox.mitre.org/XMLSchema/objects/Pipe/2.1/Pipe_Object.xsd',
	'http://cybox.mitre.org/objects#URIObject-2': 'http://cybox.mitre.org/XMLSchema/objects/URI/2.1/URI_Object.xsd',
	'http://cybox.mitre.org/objects#WinRegistryKeyObject-2': 'http://cybox.mitre.org/XMLSchema/objects/Win_Registry_Key/2.1/Win_Registry_Key_Object.xsd',
	'http://data-marking.mitre.org/Marking-1': 'http://stix.mitre.org/XMLSchema/data_marking/1.1.1/data_marking.xsd',
	'http://data-marking.mitre.org/extensions/MarkingStructure#TLP-1': 'http://stix.mitre.org/XMLSchema/extensions/marking/tlp/1.1.1/tlp_marking.xsd',
	'http://stix.mitre.org/ExploitTarget-1': 'http://stix.mitre.org/XMLSchema/exploit_target/1.1.1/exploit_target.xsd',
	'http://stix.mitre.org/Incident-1': 'http://stix.mitre.org/XMLSchema/incident/1.1.1/incident.xsd',
	'http://stix.mitre.org/Indicator-2': 'http://stix.mitre.org/XMLSchema/indicator/2.1.1/indicator.xsd',
	'http://stix.mitre.org/TTP-1': 'http://stix.mitre.org/XMLSchema/ttp/1.1.1/ttp.xsd',
	'http://stix.mitre.org/ThreatActor-1': 'http://stix.mitre.org/XMLSchema/threat_actor/1.1.1/threat_actor.xsd',
	'http://stix.mitre.org/common-1': 'http://stix.mitre.org/XMLSchema/common/1.1.1/stix_common.xsd',
	'http://stix.mitre.org/default_vocabularies-1': 'http://stix.mitre.org/XMLSchema/default_vocabularies/1.1.1/stix_default_vocabularies.xsd',
	'http://stix.mitre.org/extensions/Identity#CIQIdentity3.0-1': 'http://stix.mitre.org/XMLSchema/extensions/identity/ciq_3.0/1.1.1/ciq_3.0_identity.xsd',
	'http://stix.mitre.org/extensions/TestMechanism#Snort-1': 'http://stix.mitre.org/XMLSchema/extensions/test_mechanism/snort/1.1.1/snort_test_mechanism.xsd',
	'http://stix.mitre.org/stix-1': 'http://stix.mitre.org/XMLSchema/core/1.1.1/stix_core.xsd',
	'urn:oasis:names:tc:ciq:xal:3': 'http://stix.mitre.org/XMLSchema/external/oasis_ciq_3.0/xAL.xsd',
	'urn:oasis:names:tc:ciq:xnl:3': 'http://stix.mitre.org/XMLSchema/external/oasis_ciq_3.0/xNL.xsd',
	'urn:oasis:names:tc:ciq:xpil:3': 'http://stix.mitre.org/XMLSchema/external/oasis_ciq_3.0/xPIL.xsd',
}

def main(args):
    if len(sys.argv) < 4:
        sys.exit("Invalid parameters")
    namespace = [sys.argv[1], sys.argv[2]]
    NS_DICT[namespace[0]]=namespace[1]
    stix.utils.idgen.set_id_namespace({namespace[0]: namespace[1]})
    stix_package = STIXPackage()
    stix_header = STIXHeader()
    stix_header.title="Export from " + namespace[1] + " MISP"
    stix_header.package_intents="Threat Report"
    stix_package.stix_header = stix_header
    if sys.argv[3] == 'json':
        stix_string = stix_package.to_json()[:-1]
        stix_string += ', "related_packages": ['
    else:
        stix_string = stix_package.to_xml(auto_namespace=False, ns_dict=NS_DICT, schemaloc_dict=SCHEMALOC_DICT)
        stix_string = stix_string.replace("</stix:STIX_Package>\n", "");
    print(stix_string)

if __name__ == "__main__":
    main(sys.argv)
<|endoftext|>


<commit_before>from django.contrib import admin

from coding.models import (
        Sample, AssignmentTag, CodingAssignment, CodingAssignmentActivity)

class SampleAdmin(admin.ModelAdmin):
    filter_horizontal = ("pieces",)

admin.site.register(Sample, SampleAdmin)

class AssignmentTagAdmin(admin.ModelAdmin):
    list_filter = ("study",)
    list_display = ("name",  "study",)

admin.site.register(AssignmentTag, AssignmentTagAdmin)

class CodingAssignmentAdmin(admin.ModelAdmin):
    list_filter = ("coder", "tags", "piece__tags", "sample", "state")
    list_display = (
            "piece",  "coder",
            "sample", "state", "creation_time")

    search_fields = ("piece__id", "piece__title", "sample__name")

    filter_horizontal = ("tags",)

admin.site.register(CodingAssignment, CodingAssignmentAdmin)

class CodingAssignmentActivityAdmin(admin.ModelAdmin):
    search_fields = (
            "assignment__piece__id",
            "assignment__piece__title",
            "actor__name",
            )

    list_display = ("assignment", "action_time", "actor", "action", "state")
    list_filter = ("actor", "action", "state")

    date_hierarchy = "action_time"

admin.site.register(CodingAssignmentActivity, CodingAssignmentActivityAdmin)
<commit_msg>Use raw_id_fields to fix admin perf issues<commit_after>from django.contrib import admin

from coding.models import (
        Sample, AssignmentTag, CodingAssignment, CodingAssignmentActivity)

class SampleAdmin(admin.ModelAdmin):
    filter_horizontal = ("pieces",)

admin.site.register(Sample, SampleAdmin)

class AssignmentTagAdmin(admin.ModelAdmin):
    list_filter = ("study",)
    list_display = ("name",  "study",)

admin.site.register(AssignmentTag, AssignmentTagAdmin)

class CodingAssignmentAdmin(admin.ModelAdmin):
    list_filter = ("coder", "tags", "piece__tags", "sample", "state")
    list_display = (
            "piece",  "coder",
            "sample", "state", "creation_time")

    search_fields = ("piece__id", "piece__title", "sample__name")

    filter_horizontal = ("tags",)

    raw_id_fields = ("piece",)

admin.site.register(CodingAssignment, CodingAssignmentAdmin)

class CodingAssignmentActivityAdmin(admin.ModelAdmin):
    search_fields = (
            "assignment__piece__id",
            "assignment__piece__title",
            "actor__name",
            )

    list_display = ("assignment", "action_time", "actor", "action", "state")
    list_filter = ("actor", "action", "state")

    date_hierarchy = "action_time"

    raw_id_fields = ("assignment",)

admin.site.register(CodingAssignmentActivity, CodingAssignmentActivityAdmin)
<|endoftext|>


<commit_before>from discord.ext import commands
from datetime import datetime
import discord
from .utils import checks
import aiohttp
from urllib.parse import parse_qs
from lxml import etree

def date(argument):
    formats = (
        '%Y/%m/%d',
        '%Y-%m-%d',
    )

    for fmt in formats:
        try:
            return datetime.strptime(argument, fmt)
        except ValueError:
            continue

    raise commands.BadArgument('Cannot convert to date. Expected YYYY/MM/DD or YYYY-MM-DD.')

class Buttons:
    """Buttons that make you feel."""

    def __init__(self, bot):
        self.bot = bot

    @commands.command(hidden=True)
    async def feelgood(self):
        """press"""
        await self.bot.say('*pressed*')

    @commands.command(hidden=True)
    async def feelbad(self):
        """depress"""
        await self.bot.say('*depressed*')

    @commands.command()
    async def love(self):
        """What is love?"""
        await self.bot.say('http://i.imgur.com/JthwtGA.png')

    @commands.command(hidden=True)
    async def bored(self):
        """boredom looms"""
        await self.bot.say('http://i.imgur.com/BuTKSzf.png')

    @commands.command(pass_context=True)
    @checks.mod_or_permissions(manage_messages=True)
    async def nostalgia(self, ctx, date: date, *, channel: discord.Channel = None):
        """Pins an old message from a specific date.

        If a channel is not given, then pins from the channel the
        command was ran on.

        The format of the date must be either YYYY-MM-DD or YYYY/MM/DD.
        """

        if channel is None:
            channel = ctx.message.channel

        async for m in self.bot.logs_from(channel, after=date, limit=1):
            try:
                await self.bot.pin_message(m)
            except:
                await self.bot.say('\N{THUMBS DOWN SIGN} Could not pin message.')
            else:
                await self.bot.say('\N{THUMBS UP SIGN} Successfully pinned message.')

    @nostalgia.error
    async def nostalgia_error(self, error, ctx):
        if type(error) is commands.BadArgument:
            await self.bot.say(error)

    async def get_google_entries(self, query):
        params = {
            'q': query,
            'safe': 'on'
        }
        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; Win64; x64)'
        }

        # list of URLs
        entries = []

        async with aiohttp.get('https://www.google.com/search', params=params, headers=headers) as resp:
            if resp.status != 200:
                raise RuntimeError('Google somehow failed to respond.')

            root = etree.fromstring(await resp.text(), etree.HTMLParser())

            """
            Tree looks like this.. sort of..

                ...

                    <a href="/url?q=<url>" ...>title

                ...

                    date here
                    summary here, can contain tag

            """

            search_nodes = root.findall(".//div[@class='g']")
            for node in search_nodes:
                url_node = node.find('.//h3/a')
                if url_node is None:
                    continue

                url = url_node.attrib['href']
                if not url.startswith('/url?'):
                    continue

                url = parse_qs(url[5:])['q'][0] # get the URL from ?q query string

                # if I ever cared about the description, this is how
                entries.append(url)

                # short = node.find(".//span[@class='st']")
                # if short is None:
                #     entries.append((url, ''))
                # else:
                #     text = ''.join(short.itertext())
                #     entries.append((url, text.replace('...', '')))

        return entries

    @commands.command(aliases=['google'])
    async def g(self, *, query):
        """Searches google and gives you top result."""
        try:
            entries = await self.get_google_entries(query)
        except RuntimeError as e:
            await self.bot.say(str(e))
        else:
            next_two = entries[1:3]
            if next_two:
                formatted = '\n'.join(map(lambda x: '<%s>' % x, next_two))
                msg = '{}\n\n**See also:**\n{}'.format(entries[0], formatted)
            else:
                msg = entries[0]

            await self.bot.say(msg)

def setup(bot):
    bot.add_cog(Buttons(bot))
<commit_msg>Parse cards for `?g`.<commit_after>from discord.ext import commands
from datetime import datetime
import discord
from .utils import checks
import aiohttp
from urllib.parse import parse_qs
from lxml import etree

def date(argument):
    formats = (
        '%Y/%m/%d',
        '%Y-%m-%d',
    )

    for fmt in formats:
        try:
            return datetime.strptime(argument, fmt)
        except ValueError:
            continue

    raise commands.BadArgument('Cannot convert to date. Expected YYYY/MM/DD or YYYY-MM-DD.')

class Buttons:
    """Buttons that make you feel."""

    def __init__(self, bot):
        self.bot = bot

    @commands.command(hidden=True)
    async def feelgood(self):
        """press"""
        await self.bot.say('*pressed*')

    @commands.command(hidden=True)
    async def feelbad(self):
        """depress"""
        await self.bot.say('*depressed*')

    @commands.command()
    async def love(self):
        """What is love?"""
        await self.bot.say('http://i.imgur.com/JthwtGA.png')

    @commands.command(hidden=True)
    async def bored(self):
        """boredom looms"""
        await self.bot.say('http://i.imgur.com/BuTKSzf.png')

    @commands.command(pass_context=True)
    @checks.mod_or_permissions(manage_messages=True)
    async def nostalgia(self, ctx, date: date, *, channel: discord.Channel = None):
        """Pins an old message from a specific date.

        If a channel is not given, then pins from the channel the
        command was ran on.

        The format of the date must be either YYYY-MM-DD or YYYY/MM/DD.
        """

        if channel is None:
            channel = ctx.message.channel

        async for m in self.bot.logs_from(channel, after=date, limit=1):
            try:
                await self.bot.pin_message(m)
            except:
                await self.bot.say('\N{THUMBS DOWN SIGN} Could not pin message.')
            else:
                await self.bot.say('\N{THUMBS UP SIGN} Successfully pinned message.')

    @nostalgia.error
    async def nostalgia_error(self, error, ctx):
        if type(error) is commands.BadArgument:
            await self.bot.say(error)

    def parse_google_card(self, node):
        if node is None:
            return None

        e = discord.Embed(colour=0x738bd7)

        # check if it's a calculator card:
        calculator = node.find(".//table/tr/td/span[@class='nobr']/h2[@class='r']")
        if calculator is not None:
            e.title = 'Calculator'
            e.description = ''.join(calculator.itertext())
            return e

        parent = node.getparent()

        # check for unit conversion card
        unit = parent.find(".//ol//div[@class='_Tsb']")
        if unit is not None:
            e.title = 'Unit Conversion'
            e.description = ''.join(''.join(n.itertext()) for n in unit)
            return e

        # check for currency conversion card
        currency = parent.find(".//ol/table[@class='std _tLi']/tr/td/h2")
        if currency is not None:
            e.title = 'Currency Conversion'
            e.description = ''.join(currency.itertext())
            return e

        # check for weather card
        # this one is the most complicated of the group lol
        # everything is under a
 which has a
        #
{{ weather for place }}

        # string, the rest is fucking table fuckery.
        weather = parent.find(".//ol//div[@class='e']")
        if weather is None:
            return None

        location = weather.find('h3')
        if location is None:
            return None

        e.title = ''.join(location.itertext())

        table = weather.find('table')
        if table is None:
            return None

        # This is gonna be a bit fucky.
        # So the part we care about is on the second data
        # column of the first tr
        try:
            tr = table[0]
            img = tr[0].find('img')
            category = img.get('alt')
            image = 'https:' + img.get('src')
            temperature = tr[1].xpath("./span[@class='wob_t']//text()")[0]
        except:
            return None # RIP
        else:
            e.set_thumbnail(url=image)
            e.description = '*%s*' % category
            e.add_field(name='Temperature', value=temperature)

        # On the 4th column it tells us our wind speeds
        try:
            wind = ''.join(table[3].itertext()).replace('Wind: ', '')
        except:
            return None
        else:
            e.add_field(name='Wind', value=wind)

        # On the 5th column it tells us our humidity
        try:
            humidity = ''.join(table[4][0].itertext()).replace('Humidity: ', '')
        except:
            return None
        else:
            e.add_field(name='Humidity', value=humidity)

        return e

    async def get_google_entries(self, query):
        params = {
            'q': query,
            'safe': 'on'
        }
        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; Win64; x64)'
        }

        # list of URLs
        entries = []

        # the result of a google card, an embed
        card = None

        async with aiohttp.get('https://www.google.com/search', params=params, headers=headers) as resp:
            if resp.status != 200:
                raise RuntimeError('Google somehow failed to respond.')

            root = etree.fromstring(await resp.text(), etree.HTMLParser())

            with open('google.html', 'w', encoding='utf-8') as f:
                f.write(etree.tostring(root, pretty_print=True).decode('utf-8'))

            """
            Tree looks like this.. sort of..

                ...

                    <a href="/url?q=<url>" ...>title

                ...

                    date here
                    summary here, can contain tag

            """

            card_node = root.find(".//div[@id='topstuff']")
            card = self.parse_google_card(card_node)

            search_nodes = root.findall(".//div[@class='g']")
            for node in search_nodes:
                url_node = node.find('.//h3/a')
                if url_node is None:
                    continue

                url = url_node.attrib['href']
                if not url.startswith('/url?'):
                    continue

                url = parse_qs(url[5:])['q'][0] # get the URL from ?q query string

                # if I ever cared about the description, this is how
                entries.append(url)

                # short = node.find(".//span[@class='st']")
                # if short is None:
                #     entries.append((url, ''))
                # else:
                #     text = ''.join(short.itertext())
                #     entries.append((url, text.replace('...', '')))

        return card, entries

    @commands.command(aliases=['google'])
    async def g(self, *, query):
        """Searches google and gives you top result."""
        try:
            card, entries = await self.get_google_entries(query)
        except RuntimeError as e:
            await self.bot.say(str(e))
        else:
            if card:
                msg = '\n'.join(map(lambda x: '<%s>' % x, entries[:3]))
                card.add_field(name='Search Results', value='\n'.join(entries[:3]), inline=False)
                return await self.bot.say(embed=card)

            next_two = entries[1:3]
            if next_two:
                formatted = '\n'.join(map(lambda x: '<%s>' % x, next_two))
                msg = '{}\n\n**See also:**\n{}'.format(entries[0], formatted)
            else:
                msg = entries[0]

            await self.bot.say(msg)

def setup(bot):
    bot.add_cog(Buttons(bot))
<|endoftext|>


<commit_before>"""
This cog is to be used primarily for small janitorial tasks
(removing clover once member is hit, pruning clovers)
"""
import discord
from discord import AuditLogAction
from discord.ext import commands
from .utils import checks
from .utils.enums import Change
from datetime import datetime, timedelta
import asyncio

class Janitor():
    """
    The main class wrapepr
    """
    def __init__(self, bot):
        super().__init__()
        self.bot = bot
        self.owner = None
        try:
            self.bg_task = self.bot.loop.create_task(self.daily_prune())
            self.owner_task = self.bot.loop.create_task(self.setup_owner_dm())
        except Exception as e:
            self.bot.logger.warning(f'Error starting task prune_clovers: {e}')

    async def setup_owner_dm(self):
        await self.bot.wait_until_ready()
        self.bot.logger.info(
            f'Setting up owner channel {self.bot.bot_owner_id}')
        try:
            self.server_logs = self.bot.get_channel(378684962934751239)
            self.owner = await self.bot.get_user_info(self.bot.bot_owner_id)
        except Exception as e:
            self.bot.logger.warning(f'Error getting owner: {e}')
        self.bot.logger.info(f'User retrieved: {self.owner.name}')
        try:
            try:
                await self.owner.create_dm()
            except Exception as e:
                self.bot.logger.warning(f'Error creating dm channel: {e}')
            await self.owner.dm_channel.send('Bot started successfully')
            #await self.server_logs.send('Bot started successfully')
        except Exception as e:
            self.bot.logger.warning(f'Error getting dm channel: {e}')

    def remove_clover(self, member) -> list:
        member_roles = member.roles.copy()
        for index, role in enumerate(member_roles):
            if role.name.lower() == 'clover' or role.name.lower() == 'dedicated':
                del member_roles[index]
        return member_roles

    def remove_key(self, member) -> list:
        member_roles = member.roles.copy()
        for index, role in enumerate(member_roles):
            if role.name.lower() == '🔑':
                key_index = index
        del member_roles[key_index]
        return member_roles

    def remove_access(self, member) -> list:
        member_roles = member.roles.copy()
        role_list = []
        for index, role in enumerate(member_roles):
            if role.name.lower() in ['muse', 'dev','lewd', 'swole', 'artsy', 'shokugeki', 'degenerate', 'simulcast', 'legacy', 'meta', 'stylish']:
                role_list.append(role)
        for role in role_list:
            member_roles.remove(role)
        return member_roles

    async def on_message(self, message):
        if message.author.bot:
            return
        if message.content.startswith('.iam'):
            return
        has_clover = False
        has_key = False
        has_member = False
        has_dedicated = False
        has_permrole = False
        member_roles = message.author.roles
        for index, role in enumerate(member_roles):
            if role.name.lower() == 'clover':
                has_clover = True
            elif role.name.lower() == '🔑':
                has_key = True
            elif role.name.lower() == 'dedicated':
                has_dedicated = True
            elif role.name.lower() in ['muse', 'dev','lewd', 'swole', 'artsy', 'shokugeki', 'degenerate', 'simulcast', 'legacy', 'meta', 'stylish']:
                has_permrole = True
            elif role.name.lower() == 'member':
                has_member = True
        if has_clover and has_member:
            member_roles = self.remove_clover(message.author)
            try:
                await message.author.edit(
                    roles=member_roles,
                    reason="User upgraded from clover to member")
                await message.add_reaction('🎊')
                self.bot.logger.info(
                    f'{message.author.display_name}'
                    ' was just promoted to member!')
                try:
                    await self.bot.postgres_controller.insert_rolechange(
                        message.guild.id, message.author.id, Change.PROMOTION
                    )
                except Exception as e:
                    self.bot.logger.warning(
                        f'Issue logging action to db: {e})')
            except Exception as e:
                self.bot.logger.warning(
                    f'Error updating users roles: {e}')
        if has_key and has_member:
            member_roles = self.remove_key(message.author)
            try:
                await message.author.edit(
                    roles=member_roles,
                    reason="User upgraded from Key to member")
                self.bot.logger.info(
                    f'{message.author.display_name}'
                    ' was just promoted to member!')
                try:
                    await self.bot.postgres_controller.insert_rolechange(
                        message.guild.id, message.author.id, Change.PROMOTION
                    )
                except Exception as e:
                    self.bot.logger.warning(
                        f'Issue logging action to db: {e})')
            except Exception as e:
                self.bot.logger.warning(
                    f'Error updating users roles: {e}')
        if has_dedicated and has_permrole:
            member_roles = self.remove_access(message.author)
            try:
                await message.author.edit(
                    roles=member_roles,
                    reason="User had perm roles")
                self.bot.logger.info(
                    f'{message.author.display_name}'
                    ' had access roles removed')
            except Exception as e:
                self.bot.logger.warning(
                    f'Error updating users access roles: {e}')

    @commands.command(hidden=True)
    @checks.has_permissions(manage_roles=True)
    async def aggroprune(self,ctx):
        self.bot.logger.info(f'Prune requested by: {ctx.message.author}')
        await self.prune_nonclovers()

    @commands.command(hidden=True)
    @checks.has_permissions(manage_roles=True)
    async def prune(self, ctx):
        self.bot.logger.info(f'Prune requested by: {ctx.message.author}')
        await self.prune_clovers()

    @prune.error
    async def prune_error(self, ctx, error):
        if isinstance(error, commands.errors.CheckFailure):
            self.bot.logger.warning(f'{ctx.message.author} '
                                    'tried to run prune w/o permissions')

    @aggroprune.error
    async def aggroprune_error(self, ctx, error):
        if isinstance(error, commands.errors.CheckFailure):
            self.bot.logger.warning(f'{ctx.message.author} '
                                    'tried to run prune w/o permissions')

    async def daily_prune(self):
        self.bot.logger.info("Starting prune task, first prune in 24 hours")
        while not self.bot.is_closed():
            await asyncio.sleep(86400)
            await self.prune_clovers()

    async def prune_clovers(self):
        try:
            self.bot.logger.info('Starting prune task now')
        except Exception as e:
            self.bot.logger.info('tf')
        clovers = []
        clover_role = None
        mod_log = self.bot.get_channel(self.bot.mod_log)
        a_irl = self.bot.get_guild(self.bot.guild_id)
        for role in a_irl.roles:
            if role.name.lower() == 'clover':
                clover_role = role
        if not clover_role:
            self.bot.logger.warning(
                'Something went really wrong, '
                'I couldn\'t find the clover role')
            return
        clovers = clover_role.members
        try:
            members_prunable = await self.bot.postgres_controller.get_all_prunable()
        except Exception as e:
            self.bot.logger.warning(f'{e}')
        prune_info = {'pruned': False, 'amount': 0}
        self.bot.logger.info(f'{members_prunable}')
        for member in clovers:
            if member.id in members_prunable:
                try:
                    new_roles = self.remove_clover(member)
                    await member.edit(
                        roles=new_roles,
                        reason="Pruned due to inactivity"
                    )
                    prune_info['pruned'] = True
                    prune_info['amount'] += 1
                except Exception as e:
                    self.bot.logger.warning(
                        f'Error pruning clovers: {e}'
                    )
        self.bot.logger.info(f'Prune info: {prune_info}')
        try:
            await self.bot.postgres_controller.reset_message_deleted()
        except Exception as e:
            self.bot.logger.warning(f'Issue resetting spam db: {e}')
        if prune_info['pruned']:
            try:
                await mod_log.send(
                    f'Pruned {prune_info["amount"]} clovers 🍀🔫')
            except Exception as e:
                self.bot.logger.warning(
                    f'Error posting prune info to mod_log: {e}')

    async def prune_nonclovers(self):
        try:
            self.bot.logger.info('Starting aggroprune task now')
        except Exception as e:
            self.bot.logger.info('tf')
        clovers = []
        clover_role = None
        mod_log = self.bot.get_channel(self.bot.mod_log)
        a_irl = self.bot.get_guild(self.bot.guild_id)
        for role in a_irl.roles:
            if role.name.lower() == 'clover':
                clover_role = role
        if not clover_role:
            self.bot.logger.warning(
                'Something went really wrong, '
                'I couldn\'t find the clover role')
            return
        clovers = clover_role.members
        self.bot.logger.info(f'Can i get uhhh')
        try:
            members_safe = await self.bot.postgres_controller.get_all_clovers()
        except Exception as e:
            self.bot.logger.warning(f'shiiiiittt:{e}')
        self.bot.logger.info(f'uhhh {members_safe}')
        prune_info = {'pruned': False, 'amount': 0}
        for member in clovers:
            if member.id not in members_safe:
                try:
                    new_roles = self.remove_clover(member)
                    await member.edit(
                        roles=new_roles,
                        reason="Pruned due to inactivity"
                    )
                    prune_info['pruned'] = True
                    prune_info['amount'] += 1
                except Exception as e:
                    self.bot.logger.warning(
                        f'Error pruning clovers: {e}'
                    )
        self.bot.logger.info(f'Prune info: {prune_info}')
        try:
            await self.bot.postgres_controller.reset_message_deleted()
            await self.bot.postgres_controller.reset_user_reactions()
        except Exception as e:
            self.bot.logger.warning(f'Issue resetting spam db: {e}')
        if prune_info['pruned']:
            try:
                await mod_log.send(
                    f'Pruned {prune_info["amount"]} clovers 🍀🔫')
            except Exception as e:
                self.bot.logger.warning(
                    f'Error posting prune info to mod_log: {e}')

    @commands.command()
    @commands.has_permissions(create_channel=True)
    async def month_end(self, ctx):
        """
        help? lmao bruh thats it. just run it at the end of the month and u good
        """
        roles_to_wipe = ['member', 'active', 'regular', 'contributor', 'addicted', 'insomniac', 'no-lifer']
        local_embed = discord.Embed(
            title=f'Resetting Roles...',
            description=f'Please be patient, this might take a while...'
        )
        value_string =f'Clearing :key: role ... \n'\
                      f'Resetting *Member* role ...\n'\
                      f'Resetting *Active* role ...\n'\
                      f'Resetting *Regular* role ...\n'\
                      f'Resetting *Contributor* role ...\n'\
                      f'Resetting *Addicted* role ...\n'\
                      f'Resetting *Insomniac* role ...\n'\
                      f'Resetting *No-Lifer* role ...\n'
        local_embed.add_field(name="Progress:",
                value=value_string)
        values = value_string.split('\n')
        for index, val in enumerate(values):
            values[index] = val +'\n'
        message = await ctx.send(embed=local_embed)
        key_role = None
        for role in ctx.channel.guild.roles:
            if role.name.lower() == '🔑':
                key_role = role
        try:
            await self.rem_all_members(ctx, key_role)
            values[0] = f'Clearing :key: role :white_check_mark: \n'
            l_embed = message.embeds[0]
            l_embed.set_field_at(0 , name="Progress:", value="".join(values))
            await message.edit(embed = l_embed)
        except Exception as e:
            self.bot.logger.warning(f'Error cleaning :key: {e}')
            values[0] = f'Clearing :key: role :x: \n'
            l_embed = message.embeds[0]
            l_embed.set_field_at(0, name="Progress:", value="".join(values))
            await message.edit(embed = l_embed)
        for counter, role in enumerate(roles_to_wipe):
            members = await self.get_all_members(ctx, role)
            if not members:
                values[counter+1] = f'Resetting **{role.title()}** role :x:\n'
                l_embed = message.embeds[0]
                l_embed.set_field_at(0, name="Progress:", value="".join(values))
                await message.edit(embed = l_embed)
                self.bot.logger.warning(f'ayy yo i didn"t find this one {role}')
                continue
            for member in members:
                try:
                    temp_roles = self.rem_role(member, role)
                    temp_roles = self.add_role(temp_roles, key_role)
                    await member.edit(roles=temp_roles)
                except Exception as e:
                    self.bot.logger.warning(f'Error removing {role} from {member}:\n\n{e}')
            values[counter+1] = f'Resetting **{role.title()}** role :white_check_mark:\n'
            l_embed = message.embeds[0]
            l_embed.set_field_at(0, name="Progress:", value="".join(values))
            await message.edit(embed = l_embed)

    async def rem_all_members(self, ctx, rem_role):
        for member in rem_role.members:
            roles_list = member.roles.copy()
            for counter, role in enumerate(roles_list):
                if role == rem_role:
                    del roles_list[counter]
                    await member.edit(roles=roles_list)
                    continue

    async def get_all_members(self, ctx, role_name):
        found_role = None
        for role in ctx.channel.guild.roles:
            if role.name.lower() == role_name:
                found_role = role
        if not found_role:
            return None
        return found_role.members

    def add_role(self, role_list, role):
        role_list.append(role)
        return role_list

    def rem_role(self, member, role_name) -> list:
        key_index = None
        member_roles = member.roles.copy()
        for index, role in enumerate(member_roles):
            if role.name.lower() == role_name:
                key_index = index
        del member_roles[key_index]
        return member_roles
<commit_msg>fixed dedicated bug<commit_after>"""
This cog is to be used primarily for small janitorial tasks
(removing clover once member is hit, pruning clovers)
"""
import discord
from discord import AuditLogAction
from discord.ext import commands
from .utils import checks
from .utils.enums import Change
from datetime import datetime, timedelta
import asyncio

class Janitor():
    """
    The main class wrapepr
    """
    def __init__(self, bot):
        super().__init__()
        self.bot = bot
        self.owner = None
        try:
            self.bg_task = self.bot.loop.create_task(self.daily_prune())
            self.owner_task = self.bot.loop.create_task(self.setup_owner_dm())
        except Exception as e:
            self.bot.logger.warning(f'Error starting task prune_clovers: {e}')

    async def setup_owner_dm(self):
        await self.bot.wait_until_ready()
        self.bot.logger.info(
            f'Setting up owner channel {self.bot.bot_owner_id}')
        try:
            self.server_logs = self.bot.get_channel(378684962934751239)
            self.owner = await self.bot.get_user_info(self.bot.bot_owner_id)
        except Exception as e:
            self.bot.logger.warning(f'Error getting owner: {e}')
        self.bot.logger.info(f'User retrieved: {self.owner.name}')
        try:
            try:
                await self.owner.create_dm()
            except Exception as e:
                self.bot.logger.warning(f'Error creating dm channel: {e}')
            await self.owner.dm_channel.send('Bot started successfully')
            #await self.server_logs.send('Bot started successfully')
        except Exception as e:
            self.bot.logger.warning(f'Error getting dm channel: {e}')

    def remove_clover(self, member) -> list:
        member_roles = member.roles.copy()
        for index, role in enumerate(member_roles):
            if role.name.lower() == 'clover':
                del member_roles[index]
        return member_roles

    def remove_clover_prune(self, member) -> list:
        member_roles = member.roles.copy()
        for index, role in enumerate(member_roles):
            if role.name.lower() == 'clover' or role.name.lower() == 'dedicated':
                del member_roles[index]
        return member_roles

    def remove_key(self, member) -> list:
        member_roles = member.roles.copy()
        for index, role in enumerate(member_roles):
            if role.name.lower() == '🔑':
                key_index = index
        del member_roles[key_index]
        return member_roles

    def remove_access(self, member) -> list:
        member_roles = member.roles.copy()
        role_list = []
        for index, role in enumerate(member_roles):
            if role.name.lower() in ['muse', 'dev','lewd', 'swole', 'artsy', 'shokugeki', 'degenerate', 'simulcast', 'legacy', 'meta', 'stylish']:
                role_list.append(role)
        for role in role_list:
            member_roles.remove(role)
        return member_roles

    async def on_message(self, message):
        if message.author.bot:
            return
        if message.content.startswith('.iam'):
            return
        has_clover = False
        has_key = False
        has_member = False
        has_dedicated = False
        has_permrole = False
        member_roles = message.author.roles
        for index, role in enumerate(member_roles):
            if role.name.lower() == 'clover':
                has_clover = True
            elif role.name.lower() == '🔑':
                has_key = True
            elif role.name.lower() == 'dedicated':
                has_dedicated = True
            elif role.name.lower() in ['muse', 'dev','lewd', 'swole', 'artsy', 'shokugeki', 'degenerate', 'simulcast', 'legacy', 'meta', 'stylish']:
                has_permrole = True
            elif role.name.lower() == 'member':
                has_member = True
        if has_clover and has_member:
            member_roles = self.remove_clover(message.author)
            try:
                await message.author.edit(
                    roles=member_roles,
                    reason="User upgraded from clover to member")
                await message.add_reaction('🎊')
                self.bot.logger.info(
                    f'{message.author.display_name}'
                    ' was just promoted to member!')
                try:
                    await self.bot.postgres_controller.insert_rolechange(
                        message.guild.id, message.author.id, Change.PROMOTION
                    )
                except Exception as e:
                    self.bot.logger.warning(
                        f'Issue logging action to db: {e})')
            except Exception as e:
                self.bot.logger.warning(
                    f'Error updating users roles: {e}')
        if has_key and has_member:
            member_roles = self.remove_key(message.author)
            try:
                await message.author.edit(
                    roles=member_roles,
                    reason="User upgraded from Key to member")
                self.bot.logger.info(
                    f'{message.author.display_name}'
                    ' was just promoted to member!')
                try:
                    await self.bot.postgres_controller.insert_rolechange(
                        message.guild.id, message.author.id, Change.PROMOTION
                    )
                except Exception as e:
                    self.bot.logger.warning(
                        f'Issue logging action to db: {e})')
            except Exception as e:
                self.bot.logger.warning(
                    f'Error updating users roles: {e}')
        if has_dedicated and has_permrole:
            member_roles = self.remove_access(message.author)
            try:
                await message.author.edit(
                    roles=member_roles,
                    reason="User had perm roles")
                self.bot.logger.info(
                    f'{message.author.display_name}'
                    ' had access roles removed')
            except Exception as e:
                self.bot.logger.warning(
                    f'Error updating users access roles: {e}')

    @commands.command(hidden=True)
    @checks.has_permissions(manage_roles=True)
    async def aggroprune(self,ctx):
        self.bot.logger.info(f'Prune requested by: {ctx.message.author}')
        await self.prune_nonclovers()

    @commands.command(hidden=True)
    @checks.has_permissions(manage_roles=True)
    async def prune(self, ctx):
        self.bot.logger.info(f'Prune requested by: {ctx.message.author}')
        await self.prune_clovers()

    @prune.error
    async def prune_error(self, ctx, error):
        if isinstance(error, commands.errors.CheckFailure):
            self.bot.logger.warning(f'{ctx.message.author} '
                                    'tried to run prune w/o permissions')

    @aggroprune.error
    async def aggroprune_error(self, ctx, error):
        if isinstance(error, commands.errors.CheckFailure):
            self.bot.logger.warning(f'{ctx.message.author} '
                                    'tried to run prune w/o permissions')

    async def daily_prune(self):
        self.bot.logger.info("Starting prune task, first prune in 24 hours")
        while not self.bot.is_closed():
            await asyncio.sleep(86400)
            await self.prune_clovers()

    async def prune_clovers(self):
        try:
            self.bot.logger.info('Starting prune task now')
        except Exception as e:
            self.bot.logger.info('tf')
        clovers = []
        clover_role = None
        mod_log = self.bot.get_channel(self.bot.mod_log)
        a_irl = self.bot.get_guild(self.bot.guild_id)
        for role in a_irl.roles:
            if role.name.lower() == 'clover':
                clover_role = role
        if not clover_role:
            self.bot.logger.warning(
                'Something went really wrong, '
                'I couldn\'t find the clover role')
            return
        clovers = clover_role.members
        try:
            members_prunable = await self.bot.postgres_controller.get_all_prunable()
        except Exception as e:
            self.bot.logger.warning(f'{e}')
        prune_info = {'pruned': False, 'amount': 0}
        self.bot.logger.info(f'{members_prunable}')
        for member in clovers:
            if member.id in members_prunable:
                try:
                    new_roles = self.remove_clover_prune(member)
                    await member.edit(
                        roles=new_roles,
                        reason="Pruned due to inactivity"
                    )
                    prune_info['pruned'] = True
                    prune_info['amount'] += 1
                except Exception as e:
                    self.bot.logger.warning(
                        f'Error pruning clovers: {e}'
                    )
        self.bot.logger.info(f'Prune info: {prune_info}')
        try:
            await self.bot.postgres_controller.reset_message_deleted()
        except Exception as e:
            self.bot.logger.warning(f'Issue resetting spam db: {e}')
        if prune_info['pruned']:
            try:
                await mod_log.send(
                    f'Pruned {prune_info["amount"]} clovers 🍀🔫')
            except Exception as e:
                self.bot.logger.warning(
                    f'Error posting prune info to mod_log: {e}')

    async def prune_nonclovers(self):
        try:
            self.bot.logger.info('Starting aggroprune task now')
        except Exception as e:
            self.bot.logger.info('tf')
        clovers = []
        clover_role = None
        mod_log = self.bot.get_channel(self.bot.mod_log)
        a_irl = self.bot.get_guild(self.bot.guild_id)
        for role in a_irl.roles:
            if role.name.lower() == 'clover':
                clover_role = role
        if not clover_role:
            self.bot.logger.warning(
                'Something went really wrong, '
                'I couldn\'t find the clover role')
            return
        clovers = clover_role.members
        self.bot.logger.info(f'Can i get uhhh')
        try:
            members_safe = await self.bot.postgres_controller.get_all_clovers()
        except Exception as e:
            self.bot.logger.warning(f'shiiiiittt:{e}')
        self.bot.logger.info(f'uhhh {members_safe}')
        prune_info = {'pruned': False, 'amount': 0}
        for member in clovers:
            if member.id not in members_safe:
                try:
                    new_roles = self.remove_clover(member)
                    await member.edit(
                        roles=new_roles,
                        reason="Pruned due to inactivity"
                    )
                    prune_info['pruned'] = True
                    prune_info['amount'] += 1
                except Exception as e:
                    self.bot.logger.warning(
                        f'Error pruning clovers: {e}'
                    )
        self.bot.logger.info(f'Prune info: {prune_info}')
        try:
            await self.bot.postgres_controller.reset_message_deleted()
            await self.bot.postgres_controller.reset_user_reactions()
        except Exception as e:
            self.bot.logger.warning(f'Issue resetting spam db: {e}')
        if prune_info['pruned']:
            try:
                await mod_log.send(
                    f'Pruned {prune_info["amount"]} clovers 🍀🔫')
            except Exception as e:
                self.bot.logger.warning(
                    f'Error posting prune info to mod_log: {e}')

    @commands.command()
    @commands.has_permissions(create_channel=True)
    async def month_end(self, ctx):
        """
        help? lmao bruh thats it. just run it at the end of the month and u good
        """
        roles_to_wipe = ['member', 'active', 'regular', 'contributor', 'addicted', 'insomniac', 'no-lifer']
        local_embed = discord.Embed(
            title=f'Resetting Roles...',
            description=f'Please be patient, this might take a while...'
        )
        value_string =f'Clearing :key: role ... \n'\
                      f'Resetting *Member* role ...\n'\
                      f'Resetting *Active* role ...\n'\
                      f'Resetting *Regular* role ...\n'\
                      f'Resetting *Contributor* role ...\n'\
                      f'Resetting *Addicted* role ...\n'\
                      f'Resetting *Insomniac* role ...\n'\
                      f'Resetting *No-Lifer* role ...\n'
        local_embed.add_field(name="Progress:",
                value=value_string)
        values = value_string.split('\n')
        for index, val in enumerate(values):
            values[index] = val +'\n'
        message = await ctx.send(embed=local_embed)
        key_role = None
        for role in ctx.channel.guild.roles:
            if role.name.lower() == '🔑':
                key_role = role
        try:
            await self.rem_all_members(ctx, key_role)
            values[0] = f'Clearing :key: role :white_check_mark: \n'
            l_embed = message.embeds[0]
            l_embed.set_field_at(0 , name="Progress:", value="".join(values))
            await message.edit(embed = l_embed)
        except Exception as e:
            self.bot.logger.warning(f'Error cleaning :key: {e}')
            values[0] = f'Clearing :key: role :x: \n'
            l_embed = message.embeds[0]
            l_embed.set_field_at(0, name="Progress:", value="".join(values))
            await message.edit(embed = l_embed)
        for counter, role in enumerate(roles_to_wipe):
            members = await self.get_all_members(ctx, role)
            if not members:
                values[counter+1] = f'Resetting **{role.title()}** role :x:\n'
                l_embed = message.embeds[0]
                l_embed.set_field_at(0, name="Progress:", value="".join(values))
                await message.edit(embed = l_embed)
                self.bot.logger.warning(f'ayy yo i didn"t find this one {role}')
                continue
            for member in members:
                try:
                    temp_roles = self.rem_role(member, role)
                    temp_roles = self.add_role(temp_roles, key_role)
                    await member.edit(roles=temp_roles)
                except Exception as e:
                    self.bot.logger.warning(f'Error removing {role} from {member}:\n\n{e}')
            values[counter+1] = f'Resetting **{role.title()}** role :white_check_mark:\n'
            l_embed = message.embeds[0]
            l_embed.set_field_at(0, name="Progress:", value="".join(values))
            await message.edit(embed = l_embed)

    async def rem_all_members(self, ctx, rem_role):
        for member in rem_role.members:
            roles_list = member.roles.copy()
            for counter, role in enumerate(roles_list):
                if role == rem_role:
                    del roles_list[counter]
                    await member.edit(roles=roles_list)
                    continue

    async def get_all_members(self, ctx, role_name):
        found_role = None
        for role in ctx.channel.guild.roles:
            if role.name.lower() == role_name:
                found_role = role
        if not found_role:
            return None
        return found_role.members

    def add_role(self, role_list, role):
        role_list.append(role)
        return role_list

    def rem_role(self, member, role_name) -> list:
        key_index = None
        member_roles = member.roles.copy()
        for index, role in enumerate(member_roles):
            if role.name.lower() == role_name:
                key_index = index
        del member_roles[key_index]
        return member_roles
<|endoftext|>


<commit_before>import os
import re
import sqlite3 as lite
import time
import calendar
from datetime import datetime

import arrow
import discord
from discord.ext import commands

import parsedatetime

QUOTES_REGEX = '(["].{0,2000}["])'

def chunks(l, n):
    """Yield successive n-sized chunks from l."""
    for i in range(0, len(l), n):
        yield l[i:i + n]

def get_free_space_mb(dirname):
    """Return folder/drive free space (in megabytes)."""
    st = os.statvfs(dirname) # pylint: disable=no-member
    return st.f_bavail * st.f_frsize

class UtilityCog():
    def __init__(self, bot):
        self.bot = bot
        type(self).__name__ = "Utility"

    @commands.command(name='emote', aliases=['e'],
                      help="Get all the emotes the bot can use or a specific emote.",
                      brief="Get an emote or all of them.")
    async def emote(self, ctx, *, emote_lookup=None):
        if emote_lookup is None:
            # this code provide by xithiox
            emotes = ctx.bot.emojis
            output = ''
            for i in emotes:
                output += str(i)
                if len(output) + 33 >= 2000:
                    await ctx.send(output)
                    output = ''
            await ctx.send(output)
        else:
            emote_lookup = emote_lookup.split()
            replaced_emotes = []
            detail = False
            if "-n" in emote_lookup:
                emote_lookup.remove("-n")
                detail = True
            for emote in emote_lookup:
                emote = emote.strip(':')
                pos = discord.utils.get(ctx.bot.emojis, name=emote)
                if pos is None:
                    replaced_emotes.append(emote)
                    continue
                if detail:
                    replaced_emotes.append(f"{str(pos)} from {pos.guild} by {pos.guild.owner}")
                else:
                    replaced_emotes.append(str(pos))
            await ctx.send("".join(replaced_emotes))

    @commands.command(name='ipa', aliases=[],
                      help="Display multiple options for getting the IPA chart and/or keyboard.",
                      brief="Get the link for the IPA chart.")
    async def ipa(self, ctx):
        await ctx.send("""The IPA (International Phonetic Alphabet) chart in various forms:

<http://www.ipachart.com/> Simple version of the graph with sounds
<http://westonruter.github.io/ipa-chart/keyboard/> A keyboard site for writing all things IPA using the on-screen buttons
<https://web.uvic.ca/ling/resources/ipa/charts/IPAlab/IPAlab.htm> A more detailed version of the alphabet with interactive buttons
""")

    @commands.command(name='server', aliases=['guild', 'analyze'],
                      help="Analyze the current guild",
                      brief="Analyze the current guild")
    async def server(self, ctx):
        member_list = ctx.guild.members
        humans = 0
        bots = 0
        for i in member_list:
            if i.bot:
                bots += 1
            else:
                humans += 1
        embed = discord.Embed(
            colour=0xb8e986,
            description=f"owned by {ctx.guild.owner}"
            )
        embed.set_thumbnail(
            url=ctx.guild.icon_url
            )
        embed.set_author(
            name=ctx.guild.name,
            url=ctx.guild.icon_url
            )
        embed.set_footer(
            text="and I'm on the guild, which is the best part!"
            )
        embed.add_field(
            name=f"__{ctx.guild.member_count}__ members",
            value=f"of which __{humans}__ are humans and __{bots}__ are bots"
            )
        await ctx.send(embed=embed)

    #@commands.command(name='tag', aliases=['faq', 't'],
    #                  help="Answer FAQ",
    #                  brief="Answer FAQ")
    #async def tag(self, ctx):

    @commands.command(name='status', aliases=['test'],
                      help=("Check the status and information of the bot, "
                            "such as run time and disk space."),
                      brief="Show if the bot is still working.")
    async def status(self, ctx):
        delta_uptime = datetime.utcnow() - ctx.bot.launch_time
        hours, remainder = divmod(int(delta_uptime.total_seconds()), 3600)
        minutes, seconds = divmod(remainder, 60)
        days, hours = divmod(hours, 24)
        diskspace = get_free_space_mb("/")
        diskspaceg = diskspace / 1024 / 1024 / 1024
        embed = discord.Embed(
            title="It's working!",
            colour=0x1,
            timestamp=datetime.utcnow())
        embed.add_field(
            name="Uptime",
            value=f"{days}d, {hours}h, {minutes}m, {seconds}s",
            inline=True
            )
        embed.add_field(
            name="Latency",
            value=f"{int(ctx.bot.latency*1000)}ms",
            inline=True
            )
        embed.add_field(
            name="Remaining disk space",
            value=f"{diskspaceg:.2f} GB ({diskspace} bytes)",
            inline=True
            )
        embed.add_field(
            name=f"Present in {len(ctx.bot.guilds)} guilds",
            value=f"serving {len(ctx.bot.users)} users",
            inline=True
        )

        await ctx.send(embed=embed)

    @commands.command(name='avatar', aliases=['pfp', 'profile', 'profilepicture'],
                      help="Display your or someone else's profile picture",
                      brief="Display your avatar")
    async def avatar(self, ctx, *, target_user=None):
        if target_user is None:
            target_user = ctx.author
        else:
            target_user = await commands.MemberConverter().convert(ctx, target_user)
        avatar_url = target_user.avatar_url

        embed = discord.Embed()
        embed.set_image(
            url=avatar_url
            )
        embed.set_author(
            name=target_user.name,
            url=avatar_url
            )

        await ctx.send(embed=embed)

    @avatar.error
    async def avatar_error(self, ctx, error):
        if isinstance(error, commands.BadArgument):
            await ctx.send(f"{ctx.author.name}, {error.args[0].lower()}")

    @commands.command(name='remind', aliases=['remindme', 'r', 'reminder'],
                      help="Adds a reminder",
                      usage="<when> \"message\" OR -list OR -delete <reminder_number>")
    async def remind(self, ctx, *, input_text="1 day"):
        # If the user gives a multi-line input, only read the first one.
        input_text = input_text.split("\n")[0]
        # Split all words to delect flags in them.
        flags = input_text.split()
        # Make a sqlite3 connection to the database where reminders are stored.
        con = lite.connect("important/data.db")

        # flag detection
        if "-list" in flags:
            with con:
                # Get all database entries requested by the author
                cur = con.cursor()
                cur.execute("SELECT * FROM Reminders WHERE requester = ?", (ctx.author.id, ))
                matching = cur.fetchall()

                if not matching:
                    await ctx.send(f"{ctx.author.name}, you do not have any reminders!")
                    return

                return_message = "All reminders you have set:\n"
                # Sort by creation date for consistency
                matching.sort(key=lambda tup: arrow.get(str(tup[4])))
                for y, i in enumerate(matching):
                    arrow_time = arrow.get(str(i[2]), "YYYYMMDDHHmmss")
                    formatted_time = arrow_time.format("YYYY-MM-DD HH:mm:ss")
                    humanized_time = arrow_time.humanize()
                    # Format example: `4`: in 3 days (2018-07-02 00:18:23) - 1 year since the relay
                    return_message += f"`{y+1}`: {humanized_time} ({formatted_time}) - {i[0]}\n"
                await ctx.send(return_message)
        elif "-delete" in flags:
            # Make sure we don't read the actual flag as the value
            flags.remove('-delete')
            delete_number = flags[0]
            if delete_number == "all":
                with con:
                    # Count all the entires then delete them all
                    cur = con.cursor()
                    cur.execute("SELECT * FROM Reminders WHERE requester = ?", (ctx.author.id, ))
                    entry_amount = len(cur.fetchall())
                    cur.execute("DELETE FROM Reminders WHERE requester = ?", (ctx.author.id, ))
                    await ctx.send(f"{ctx.author.name}, deleted all {entry_amount} reminders!")
            else:
                try:
                    # Since the external list is 1-indexed but the internal list is 0-indexed
                    delete_number = int(delete_number) - 1
                except ValueError:
                    await ctx.send(f"{ctx.author.name}, integers please")
                    return
            with con:
                cur = con.cursor()
                cur.execute("SELECT * FROM Reminders WHERE requester = ?", (ctx.author.id, ))
                matching = cur.fetchall()
                # Sort by creation date for consistency
                matching.sort(key=lambda tup: arrow.get(str(tup[4])))
                try:
                    target_entry = matching[delete_number]
                except IndexError:
                    await ctx.send(f"{ctx.author.name}, reminder number out of range")
                    return
                await ctx.send((f"{ctx.author.name}, deleted entry"
                                f"`{delete_number + 1}`: {target_entry[0]}"))
                # This might delete more than one, actually, if you managed to make two reminders
                # at the exact same second somehow. This is such an edge case though,
                # so I don't worry about it.
                cur.execute(
                    "DELETE FROM Reminders WHERE request_time = ? AND requester = ?",
                    (target_entry[4], target_entry[3]))
        else:
            # If the user doesn't supply a message this one will be used
            included_message = "This is a default message"
            # parsedatetime module functionality
            cal = parsedatetime.Calendar()

            # Detect and get the message included which will need to be in quotes
            input_text_regex = re.search(QUOTES_REGEX, input_text)
            if input_text_regex:
                included_message = input_text_regex.group().strip('"')

            # Get rid of the message in the quotes and then parse the remainder with parsedatetime
            # parsedatetime returns a tuple (parsed_time, status_code)
            remind_time = re.sub(QUOTES_REGEX, '', input_text)
            remind_time = cal.parse(remind_time, datetime.utcnow())

            # Things that could go wrong
            error_message = ""
            # If parsedatetime returns status_code 0, something went wrong. This is non-fatal
            if remind_time[1] == 0:
                remind_time = cal.parse("1 day", datetime.utcnow())
                error_message = "Couldn't parse time, defaulting to 1 day\n"
            remind_time = time.gmtime(calendar.timegm((*remind_time[0][:8], time.gmtime()[8])))
            if remind_time < time.gmtime():
                await ctx.send(f"{ctx.author.name}, time is in the past.")
                return
            elif remind_time == time.gmtime():
                await ctx.send(f"{ctx.author.name}, I don't think you needed a reminder for that")
                return
            # remind_date needs to be formatted in this way for it to be comparable with other times
            # like an int.
            remind_date = time.strftime('%Y%m%d%H%M%S', remind_time)
            request_date = time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime())
            time_format = time.strftime('%Y-%m-%d %H:%M:%S', remind_time)
            #arrow_time = arrow.get(time_format).replace(tzinfo="+00:00")
            if int(remind_date) > 99991231235959:
                await ctx.send((f"sorry, time traveller {ctx.author.name}, "
                                f"but I had to set the limit to 9999-12-31 23:59:59"))
                remind_date = "99991231235959"
                time_format = "9999-12-31 23:59:59"
            await ctx.send((f"{error_message}"
                            f"{ctx.author.name}, reminding you at "
                            f"{time_format} (arrow_time.humanize()})"))
            # the discord.py library returns an invalid jump to url link that we must modify
            message_link = ctx.message.jump_url.replace('?jump=', '/')
            requester = ctx.author.id
            with con:
                cur = con.cursor()
                cur.execute(
                    "INSERT INTO Reminders VALUES(?, ?, ?, ?, ?)",
                    (included_message, message_link, remind_date, requester, request_date))

def setup(bot):
    bot.add_cog(UtilityCog(bot))
<commit_msg>fixed missing {<commit_after>import os
import re
import sqlite3 as lite
import time
import calendar
from datetime import datetime

import arrow
import discord
from discord.ext import commands

import parsedatetime

QUOTES_REGEX = '(["].{0,2000}["])'

def chunks(l, n):
    """Yield successive n-sized chunks from l."""
    for i in range(0, len(l), n):
        yield l[i:i + n]

def get_free_space_mb(dirname):
    """Return folder/drive free space (in megabytes)."""
    st = os.statvfs(dirname) # pylint: disable=no-member
    return st.f_bavail * st.f_frsize

class UtilityCog():
    def __init__(self, bot):
        self.bot = bot
        type(self).__name__ = "Utility"

    @commands.command(name='emote', aliases=['e'],
                      help="Get all the emotes the bot can use or a specific emote.",
                      brief="Get an emote or all of them.")
    async def emote(self, ctx, *, emote_lookup=None):
        if emote_lookup is None:
            # this code provide by xithiox
            emotes = ctx.bot.emojis
            output = ''
            for i in emotes:
                output += str(i)
                if len(output) + 33 >= 2000:
                    await ctx.send(output)
                    output = ''
            await ctx.send(output)
        else:
            emote_lookup = emote_lookup.split()
            replaced_emotes = []
            detail = False
            if "-n" in emote_lookup:
                emote_lookup.remove("-n")
                detail = True
            for emote in emote_lookup:
                emote = emote.strip(':')
                pos = discord.utils.get(ctx.bot.emojis, name=emote)
                if pos is None:
                    replaced_emotes.append(emote)
                    continue
                if detail:
                    replaced_emotes.append(f"{str(pos)} from {pos.guild} by {pos.guild.owner}")
                else:
                    replaced_emotes.append(str(pos))
            await ctx.send("".join(replaced_emotes))

    @commands.command(name='ipa', aliases=[],
                      help="Display multiple options for getting the IPA chart and/or keyboard.",
                      brief="Get the link for the IPA chart.")
    async def ipa(self, ctx):
        await ctx.send("""The IPA (International Phonetic Alphabet) chart in various forms:

<http://www.ipachart.com/> Simple version of the graph with sounds
<http://westonruter.github.io/ipa-chart/keyboard/> A keyboard site for writing all things IPA using the on-screen buttons
<https://web.uvic.ca/ling/resources/ipa/charts/IPAlab/IPAlab.htm> A more detailed version of the alphabet with interactive buttons
""")

    @commands.command(name='server', aliases=['guild', 'analyze'],
                      help="Analyze the current guild",
                      brief="Analyze the current guild")
    async def server(self, ctx):
        member_list = ctx.guild.members
        humans = 0
        bots = 0
        for i in member_list:
            if i.bot:
                bots += 1
            else:
                humans += 1
        embed = discord.Embed(
            colour=0xb8e986,
            description=f"owned by {ctx.guild.owner}"
            )
        embed.set_thumbnail(
            url=ctx.guild.icon_url
            )
        embed.set_author(
            name=ctx.guild.name,
            url=ctx.guild.icon_url
            )
        embed.set_footer(
            text="and I'm on the guild, which is the best part!"
            )
        embed.add_field(
            name=f"__{ctx.guild.member_count}__ members",
            value=f"of which __{humans}__ are humans and __{bots}__ are bots"
            )
        await ctx.send(embed=embed)

    #@commands.command(name='tag', aliases=['faq', 't'],
    #                  help="Answer FAQ",
    #                  brief="Answer FAQ")
    #async def tag(self, ctx):

    @commands.command(name='status', aliases=['test'],
                      help=("Check the status and information of the bot, "
                            "such as run time and disk space."),
                      brief="Show if the bot is still working.")
    async def status(self, ctx):
        delta_uptime = datetime.utcnow() - ctx.bot.launch_time
        hours, remainder = divmod(int(delta_uptime.total_seconds()), 3600)
        minutes, seconds = divmod(remainder, 60)
        days, hours = divmod(hours, 24)
        diskspace = get_free_space_mb("/")
        diskspaceg = diskspace / 1024 / 1024 / 1024
        embed = discord.Embed(
            title="It's working!",
            colour=0x1,
            timestamp=datetime.utcnow())
        embed.add_field(
            name="Uptime",
            value=f"{days}d, {hours}h, {minutes}m, {seconds}s",
            inline=True
            )
        embed.add_field(
            name="Latency",
            value=f"{int(ctx.bot.latency*1000)}ms",
            inline=True
            )
        embed.add_field(
            name="Remaining disk space",
            value=f"{diskspaceg:.2f} GB ({diskspace} bytes)",
            inline=True
            )
        embed.add_field(
            name=f"Present in {len(ctx.bot.guilds)} guilds",
            value=f"serving {len(ctx.bot.users)} users",
            inline=True
        )

        await ctx.send(embed=embed)

    @commands.command(name='avatar', aliases=['pfp', 'profile', 'profilepicture'],
                      help="Display your or someone else's profile picture",
                      brief="Display your avatar")
    async def avatar(self, ctx, *, target_user=None):
        if target_user is None:
            target_user = ctx.author
        else:
            target_user = await commands.MemberConverter().convert(ctx, target_user)
        avatar_url = target_user.avatar_url

        embed = discord.Embed()
        embed.set_image(
            url=avatar_url
            )
        embed.set_author(
            name=target_user.name,
            url=avatar_url
            )

        await ctx.send(embed=embed)

    @avatar.error
    async def avatar_error(self, ctx, error):
        if isinstance(error, commands.BadArgument):
            await ctx.send(f"{ctx.author.name}, {error.args[0].lower()}")

    @commands.command(name='remind', aliases=['remindme', 'r', 'reminder'],
                      help="Adds a reminder",
                      usage="<when> \"message\" OR -list OR -delete <reminder_number>")
    async def remind(self, ctx, *, input_text="1 day"):
        # If the user gives a multi-line input, only read the first one.
        input_text = input_text.split("\n")[0]
        # Split all words to delect flags in them.
        flags = input_text.split()
        # Make a sqlite3 connection to the database where reminders are stored.
        con = lite.connect("important/data.db")

        # flag detection
        if "-list" in flags:
            with con:
                # Get all database entries requested by the author
                cur = con.cursor()
                cur.execute("SELECT * FROM Reminders WHERE requester = ?", (ctx.author.id, ))
                matching = cur.fetchall()

                if not matching:
                    await ctx.send(f"{ctx.author.name}, you do not have any reminders!")
                    return

                return_message = "All reminders you have set:\n"
                # Sort by creation date for consistency
                matching.sort(key=lambda tup: arrow.get(str(tup[4])))
                for y, i in enumerate(matching):
                    arrow_time = arrow.get(str(i[2]), "YYYYMMDDHHmmss")
                    formatted_time = arrow_time.format("YYYY-MM-DD HH:mm:ss")
                    humanized_time = arrow_time.humanize()
                    # Format example: `4`: in 3 days (2018-07-02 00:18:23) - 1 year since the relay
                    return_message += f"`{y+1}`: {humanized_time} ({formatted_time}) - {i[0]}\n"
                await ctx.send(return_message)
        elif "-delete" in flags:
            # Make sure we don't read the actual flag as the value
            flags.remove('-delete')
            delete_number = flags[0]
            if delete_number == "all":
                with con:
                    # Count all the entires then delete them all
                    cur = con.cursor()
                    cur.execute("SELECT * FROM Reminders WHERE requester = ?", (ctx.author.id, ))
                    entry_amount = len(cur.fetchall())
                    cur.execute("DELETE FROM Reminders WHERE requester = ?", (ctx.author.id, ))
                    await ctx.send(f"{ctx.author.name}, deleted all {entry_amount} reminders!")
            else:
                try:
                    # Since the external list is 1-indexed but the internal list is 0-indexed
                    delete_number = int(delete_number) - 1
                except ValueError:
                    await ctx.send(f"{ctx.author.name}, integers please")
                    return
            with con:
                cur = con.cursor()
                cur.execute("SELECT * FROM Reminders WHERE requester = ?", (ctx.author.id, ))
                matching = cur.fetchall()
                # Sort by creation date for consistency
                matching.sort(key=lambda tup: arrow.get(str(tup[4])))
                try:
                    target_entry = matching[delete_number]
                except IndexError:
                    await ctx.send(f"{ctx.author.name}, reminder number out of range")
                    return
                await ctx.send((f"{ctx.author.name}, deleted entry"
                                f"`{delete_number + 1}`: {target_entry[0]}"))
                # This might delete more than one, actually, if you managed to make two reminders
                # at the exact same second somehow. This is such an edge case though,
                # so I don't worry about it.
                cur.execute(
                    "DELETE FROM Reminders WHERE request_time = ? AND requester = ?",
                    (target_entry[4], target_entry[3]))
        else:
            # If the user doesn't supply a message this one will be used
            included_message = "This is a default message"
            # parsedatetime module functionality
            cal = parsedatetime.Calendar()

            # Detect and get the message included which will need to be in quotes
            input_text_regex = re.search(QUOTES_REGEX, input_text)
            if input_text_regex:
                included_message = input_text_regex.group().strip('"')

            # Get rid of the message in the quotes and then parse the remainder with parsedatetime
            # parsedatetime returns a tuple (parsed_time, status_code)
            remind_time = re.sub(QUOTES_REGEX, '', input_text)
            remind_time = cal.parse(remind_time, datetime.utcnow())

            # Things that could go wrong
            error_message = ""
            # If parsedatetime returns status_code 0, something went wrong. This is non-fatal
            if remind_time[1] == 0:
                remind_time = cal.parse("1 day", datetime.utcnow())
                error_message = "Couldn't parse time, defaulting to 1 day\n"
            remind_time = time.gmtime(calendar.timegm((*remind_time[0][:8], time.gmtime()[8])))
            if remind_time < time.gmtime():
                await ctx.send(f"{ctx.author.name}, time is in the past.")
                return
            elif remind_time == time.gmtime():
                await ctx.send(f"{ctx.author.name}, I don't think you needed a reminder for that")
                return
            # remind_date needs to be formatted in this way for it to be comparable with other times
            # like an int.
            remind_date = time.strftime('%Y%m%d%H%M%S', remind_time)
            request_date = time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime())
            time_format = time.strftime('%Y-%m-%d %H:%M:%S', remind_time)
            #arrow_time = arrow.get(time_format).replace(tzinfo="+00:00")
            if int(remind_date) > 99991231235959:
                await ctx.send((f"sorry, time traveller {ctx.author.name}, "
                                f"but I had to set the limit to 9999-12-31 23:59:59"))
                remind_date = "99991231235959"
                time_format = "9999-12-31 23:59:59"
            await ctx.send((f"{error_message}"
                            f"{ctx.author.name}, reminding you at "
                            f"{time_format} ({arrow_time.humanize()})"))
            # the discord.py library returns an invalid jump to url link that we must modify
            message_link = ctx.message.jump_url.replace('?jump=', '/')
            requester = ctx.author.id
            with con:
                cur = con.cursor()
                cur.execute(
                    "INSERT INTO Reminders VALUES(?, ?, ?, ?, ?)",
                    (included_message, message_link, remind_date, requester, request_date))

def setup(bot):
    bot.add_cog(UtilityCog(bot))
<|endoftext|>


<commit_before><commit_msg>Add test for Indexed(Base) assumptions<commit_after><|endoftext|>