# Path: desertbot/message.py
# class IRCMessage(object):
#     def __init__(self, msgType: str, user: IRCUser, channel: Optional[IRCChannel], message: str, bot: 'DesertBot',
#                  metadata: Dict=None, tags: Dict=None):
#         if metadata is None:
#             metadata = {}
#         self.metadata = metadata
#         if tags is None:
#             tags = {}
#         self.tags = tags
# 
#         if isinstance(message, bytes):
#             unicodeMessage = message.decode('utf-8', 'ignore')
#         else:  # Already utf-8?
#             unicodeMessage = message
#         self.type = msgType
#         self.messageList = unicodeMessage.strip().split(' ')
#         self.messageString = unicodeMessage
#         self.user = user
# 
#         self.channel = None
#         if channel is None:
#             self.replyTo = self.user.nick
#             self.targetType = TargetTypes.USER
#         else:
#             self.channel = channel
#             # I would like to set this to the channel object but I would probably break functionality if I did :I
#             self.replyTo = channel.name
#             self.targetType = TargetTypes.CHANNEL
# 
#         self.command = ''
#         self.parameters = ''
#         self.parameterList = []
# 
#         if len(self.messageList) == 1 and self.messageList[0] == bot.commandChar:
#             self.command = ''
#         elif self.messageList[0].startswith(bot.commandChar) and self.messageList[0][:3].count(bot.commandChar) == 1:
#             self.command = self.messageList[0][len(bot.commandChar):]
#             if self.command == '':
#                 self.command = self.messageList[1]
#                 self.parameters = u' '.join(self.messageList[2:])
#             else:
#                 self.parameters = u' '.join(self.messageList[1:])
#         elif re.match('{}[:,]?'.format(re.escape(bot.nick)), self.messageList[0], re.IGNORECASE):
#             if len(self.messageList) > 1:
#                 self.command = self.messageList[1]
#                 self.parameters = u' '.join(self.messageList[2:])
#         self.command = self.command.lower()
#         if self.parameters.strip():
#             self.parameterList = self.parameters.split(' ')
# 
#             self.parameterList = [param for param in self.parameterList if param != '']
# 
#             if len(self.parameterList) == 1 and not self.parameterList[0]:
#                 self.parameterList = []
# 
# Path: desertbot/datastore.py
# class DataStore(object):
#     def __init__(self, storagePath, defaultsPath):
#         self.storagePath = storagePath
#         self.defaultsPath = defaultsPath
#         self.data = {}
#         self.load()
# 
#     def load(self):
#         # if a file data/defaults/<module>.json exists, it has priority on load
#         if os.path.exists(self.defaultsPath):
#             with open(self.defaultsPath) as storageFile:
#                 self.data = json.load(storageFile)
#         # if not, use data/<network>/<module>.json instead
#         elif os.path.exists(self.storagePath):
#             with open(self.storagePath) as storageFile:
#                 self.data = json.load(storageFile)
#         # if there's nothing, make sure the folder at least exists for the server-specific data files
#         else:
#             os.makedirs(os.path.dirname(self.storagePath), exist_ok=True)
# 
#     def save(self):
#         # don't save empty files, to keep the data directories from filling up with pointless files
#         if len(self.data) != 0:
#             tmpFile = f"{self.storagePath}.tmp"
#             with open(tmpFile, "w") as storageFile:
#                 storageFile.write(json.dumps(self.data, indent=4))
#             os.rename(tmpFile, self.storagePath)
# 
#     def __len__(self):
#         return len(self.data)
# 
#     def __iter__(self):
#         return iter(self.data)
# 
#     def __getitem__(self, item):
#         return self.data[item]
# 
#     def __setitem__(self, key, value):
#         self.data[key] = value
#         self.save()
#  
#     def __contains__(self, key):
#         return key in self.data
# 
#     def __delitem__(self, key):
#         del self.data[key]
# 
#     def items(self):
#         return self.data.items()
# 
#     def values(self):
#         return self.data.values()
# 
#     def keys(self):
#         return self.data.keys()
# 
#     def get(self, key, defaultValue=None):
#         return self.data.get(key, defaultValue)
# 
#     def pop(self, key):
#         data = self.data.pop(key)
#         self.save()
#         return data


from twisted.internet.task import LoopingCall
from zope.interface import Interface
from functools import wraps
from fnmatch import fnmatch
from typing import Any, Callable, List, Tuple, Union, TYPE_CHECKING
from desertbot.message import IRCMessage
from desertbot.datastore import DataStore
    from desertbot.desertbot import DesertBot
import os
import random
import logging

    def hookBot(self, bot: 'DesertBot') -> None:
        self.bot = bot

    def loadDataStore(self):
        dataRootPath = os.path.join(self.bot.rootDir, 'data', 'servers', self.bot.server)
        defaultRootPath = os.path.join(self.bot.rootDir, 'data', 'defaults')

        self.storage = DataStore(storagePath=os.path.join(dataRootPath, f'{self.__class__.__name__}.json'),
                                 defaultsPath=os.path.join(defaultRootPath, f'{self.__class__.__name__}.json'))

        # ensure storage is periodically synced to disk - DataStore.__set__() does call DataStore.save(), but you never know
        self.storageSync = LoopingCall(self.storage.save)
        # since each module has its own LoopingCall,
        # space them out over a second using random.random() to add 0-1 seconds to each module's storage save interval
        self.storageSync.start(self.bot.config.getWithDefault('storage_save_interval', 60) + random.random(), now=False)

    def saveDataStore(self):
        self.storage.save()

    def displayHelp(self, query: Union[List[str], None]) -> str:
        if query is not None and query[0].lower() == self.__class__.__name__.lower():
            return self.help(query)

    def help(self, query: Union[List[str], None]) -> str:
        return 'This module has no help text'

    def onUnload(self) -> None:
        self.storage.save()


# Path: desertbot/message.py
# class IRCMessage(object):
#     def __init__(self, msgType: str, user: IRCUser, channel: Optional[IRCChannel], message: str, bot: 'DesertBot',
#                  metadata: Dict=None, tags: Dict=None):
#         if metadata is None:
#             metadata = {}
#         self.metadata = metadata
#         if tags is None:
#             tags = {}
#         self.tags = tags
# 
#         if isinstance(message, bytes):
#             unicodeMessage = message.decode('utf-8', 'ignore')
#         else:  # Already utf-8?
#             unicodeMessage = message
#         self.type = msgType
#         self.messageList = unicodeMessage.strip().split(' ')
#         self.messageString = unicodeMessage
#         self.user = user
# 
#         self.channel = None
#         if channel is None:
#             self.replyTo = self.user.nick
#             self.targetType = TargetTypes.USER
#         else:
#             self.channel = channel
#             # I would like to set this to the channel object but I would probably break functionality if I did :I
#             self.replyTo = channel.name
#             self.targetType = TargetTypes.CHANNEL
# 
#         self.command = ''
#         self.parameters = ''
#         self.parameterList = []
# 
#         if len(self.messageList) == 1 and self.messageList[0] == bot.commandChar:
#             self.command = ''
#         elif self.messageList[0].startswith(bot.commandChar) and self.messageList[0][:3].count(bot.commandChar) == 1:
#             self.command = self.messageList[0][len(bot.commandChar):]
#             if self.command == '':
#                 self.command = self.messageList[1]
#                 self.parameters = u' '.join(self.messageList[2:])
#             else:
#                 self.parameters = u' '.join(self.messageList[1:])
#         elif re.match('{}[:,]?'.format(re.escape(bot.nick)), self.messageList[0], re.IGNORECASE):
#             if len(self.messageList) > 1:
#                 self.command = self.messageList[1]
#                 self.parameters = u' '.join(self.messageList[2:])
#         self.command = self.command.lower()
#         if self.parameters.strip():
#             self.parameterList = self.parameters.split(' ')
# 
#             self.parameterList = [param for param in self.parameterList if param != '']
# 
#             if len(self.parameterList) == 1 and not self.parameterList[0]:
#                 self.parameterList = []
# 
# Path: desertbot/datastore.py
# class DataStore(object):
#     def __init__(self, storagePath, defaultsPath):
#         self.storagePath = storagePath
#         self.defaultsPath = defaultsPath
#         self.data = {}
#         self.load()
# 
#     def load(self):
#         # if a file data/defaults/<module>.json exists, it has priority on load
#         if os.path.exists(self.defaultsPath):
#             with open(self.defaultsPath) as storageFile:
#                 self.data = json.load(storageFile)
#         # if not, use data/<network>/<module>.json instead
#         elif os.path.exists(self.storagePath):
#             with open(self.storagePath) as storageFile:
#                 self.data = json.load(storageFile)
#         # if there's nothing, make sure the folder at least exists for the server-specific data files
#         else:
#             os.makedirs(os.path.dirname(self.storagePath), exist_ok=True)
# 
#     def save(self):
#         # don't save empty files, to keep the data directories from filling up with pointless files
#         if len(self.data) != 0:
#             tmpFile = f"{self.storagePath}.tmp"
#             with open(tmpFile, "w") as storageFile:
#                 storageFile.write(json.dumps(self.data, indent=4))
#             os.rename(tmpFile, self.storagePath)
# 
#     def __len__(self):
#         return len(self.data)
# 
#     def __iter__(self):
#         return iter(self.data)
# 
#     def __getitem__(self, item):
#         return self.data[item]
# 
#     def __setitem__(self, key, value):
#         self.data[key] = value
#         self.save()
#  
#     def __contains__(self, key):
#         return key in self.data
# 
#     def __delitem__(self, key):
#         del self.data[key]
# 
#     def items(self):
#         return self.data.items()
# 
#     def values(self):
#         return self.data.values()
# 
#     def keys(self):
#         return self.data.keys()
# 
#     def get(self, key, defaultValue=None):
#         return self.data.get(key, defaultValue)
# 
#     def pop(self, key):
#         data = self.data.pop(key)
#         self.save()
#         return data


# Path: desertbot/moduleinterface.py
from twisted.internet.task import LoopingCall
from zope.interface import Interface
from functools import wraps
from fnmatch import fnmatch
from typing import Any, Callable, List, Tuple, Union, TYPE_CHECKING
from desertbot.message import IRCMessage
from desertbot.datastore import DataStore
    from desertbot.desertbot import DesertBot
import os
import random
import logging


    def hookBot(self, bot: 'DesertBot') -> None:
        self.bot = bot

    def loadDataStore(self):
        dataRootPath = os.path.join(self.bot.rootDir, 'data', 'servers', self.bot.server)
        defaultRootPath = os.path.join(self.bot.rootDir, 'data', 'defaults')

        self.storage = DataStore(storagePath=os.path.join(dataRootPath, f'{self.__class__.__name__}.json'),
                                 defaultsPath=os.path.join(defaultRootPath, f'{self.__class__.__name__}.json'))

        # ensure storage is periodically synced to disk - DataStore.__set__() does call DataStore.save(), but you never know
        self.storageSync = LoopingCall(self.storage.save)
        # since each module has its own LoopingCall,
        # space them out over a second using random.random() to add 0-1 seconds to each module's storage save interval
        self.storageSync.start(self.bot.config.getWithDefault('storage_save_interval', 60) + random.random(), now=False)

    def saveDataStore(self):
        self.storage.save()

    def displayHelp(self, query: Union[List[str], None]) -> str:
        if query is not None and query[0].lower() == self.__class__.__name__.lower():
            return self.help(query)

    def help(self, query: Union[List[str], None]) -> str:
        return 'This module has no help text'

    def onUnload(self) -> None:
        self.storage.save()


    def checkIgnoreList(self, message: IRCMessage) -> bool: