make the settings table act like a key-value store and make other files utilize it

This commit is contained in:
superwhiskers 2018-07-03 17:14:36 -05:00
parent ea3369edc6
commit 0fb78bdf0e
3 changed files with 131 additions and 74 deletions

View file

@ -20,21 +20,23 @@ class Modmail:
"""
# initiation function
def __init__(self, server_info):
def __init__(self, server_object):
"""
initiates the modmail class. takes one argument, a serverinfo dict (doc.py #1)
initiates the modmail class
:argument server_object: a discord.Guild
"""
# set some variables to save these
self.server = server_info # server info, from a YAML file
self.db = dataset.connect(
self.server["dbPath"] # the sqlite3 database object for the server
f"servers/{ server_object.id }/server.db" # open a connection to the server database
)
self.settings = utils.KeyValueTable(self.db["settings"]) # get the settings table as a key-value store
self.modmail = self.db["modmail"] # get the modmail table
self.log = utils.Logger(
f"db::mail { self.server['dbName'] }",
f"{ self.server['rootDir'] }/db_modmail.log",
f"db::mail ({ server_object.id }/{ server_object.name })",
f"servers/{ server_object.id }/db_modmail.log",
)
# notify the sysadmin of the connection
self.log(f"connected to { self.server['dbName'] }")
self.log(f"connected to { server_object.id }/{ server_object.name }")
# display the current number of stored message
self.log(f"current number of messages in database: { len(self.db['mail']) }")
# finish it...
@ -45,19 +47,20 @@ class Modmail:
@staticmethod
def parse(mail):
"""
parses a mail dict (doc.py #3). intended to be used by the modmail class but may have other uses.
parses a mail dict. intended to be used by the modmail class but may have other uses.
:argument mail: a mail dict
"""
# parse a list of mail
if isinstance(mail, list):
# the string to send to Discord
return_string = """mail from { self.server }:
return_string = f"""mail from { self.server.id }/{ self.server.name }:
"""
# loop through the mail list
for x in range(0, len(mail)):
# append the parsed message to the string
return_string += f"""
{mail[x]["id"]} - sent by {mail[x]["sender"]}```
{mail[x]["message"]}```
{ mail[x]["id"] } - sent by { mail["sender_id"] }/{ mail["sender_name"] }```
{ mail[x]["message"] }```
"""
# return the string
return return_string
@ -66,8 +69,8 @@ class Modmail:
else:
# the string to send to Discord
return_string = f"""
{mail["id"]} - sent by {mail["sender"]}```
{mail["message"]}```
{ mail["id"] } - sent by { mail["sender_id"] }/{ mail["sender_name"] }```
{ mail["message"] }```
"""
# return the parsed message
@ -76,19 +79,21 @@ class Modmail:
# get the current modmail
def read(self, mod):
"""
returns a generated parseMail string ready for sending to discord. takes one argument, a mod's discord id
returns a string of mail ready for sending to discord
:argument mod: a discord.Member of a mod
:returns: a string of mail ready to send to discord
"""
# tell the sysadmin that we're reading mail for a mod
self.log(f"reading mail for { mod }")
self.log(f"reading mail for { mod.id }/{ mod.name }")
# the unread mail list
unread = []
# add all of the unread mail
for mail in self.db["mail"]:
for mail in self.modmail:
try:
mail["readBy"].index(mod)
mail["readBy"].index(mod.id)
except ValueError:
unread.append(mail)
mail["readBy"].append(mod)
mail["readBy"].append(mod.id)
# if no mail is unread
if not unread:
return None
@ -99,18 +104,20 @@ class Modmail:
# send modmail
def send(self, msg, sender):
"""
send mail to mods. takes two arguments, a string for the message, and a discord id for the sender
send mail to mods
:argument msg: a string to send as a message
:argument sender: a discord.Member for the sender
"""
# tell the sysadmin that someone is sending mail
self.log(f"{ sender } is sending modmail")
self.log(f"{ sender.id }/{ sender.name } is sending modmail")
# construct the message
mail_to_send = dict(
id=utils.whiskerflake(), sender=sender, message=msg, readBy=[]
id=utils.whiskerflake(), sender_id=sender.id, sender_name=sender.name, message=msg, readBy=[]
)
# send the message
self.db["mail"].insert(mail_to_send)
self.modmail.insert(mail_to_send)
# tell the sysadmin the current number of messages in the db
self.log(f"{ sender }'s mail has been sent")
self.log(f"{ sender.id }/{ sender.name }'s mail has been sent")
self.log(f"current number of messages: { len(self.db['mail']) }")
# exit the function
return
@ -122,12 +129,12 @@ class Modmail:
"""
# tell the sysadmin that the mail is being cleaned
self.log(f"cleaning mail...")
# list of moderator's usernames
mods = self.server["mods"]
# list of moderator's ids
mods = self.settings["mod_ids"]
# indexes of mail to delete
indexes_to_delete = []
# check if all the mods have read the mail
for mail in self.db["mail"]:
for mail in self.modmail:
mail_read = True
for y in range(0, len(mods)):
try:
@ -135,38 +142,29 @@ class Modmail:
except ValueError:
mail_read = False
if mail_read:
indexes_to_delete.append(self.db["mail"].find(id=mail["id"]))
indexes_to_delete.append(self.modmail.find(id=mail["id"]))
# then clean from the mail list the indexes to delete
for x in range(0, len(indexes_to_delete)):
self.db["mail"].delete(id=x)
self.modmail.delete(id=x)
# return with nothing
return
# delete a specific message
def delete(self, sid):
"""
deletes a specific message by it's identifier. takes one arguement, an identifier. returns none if not found,
deletes a specific message by it's identifier. takes one argument, an identifier. returns none if not found,
true if it was
"""
# tell the sysadmin that mail is being deleted
self.log(f"searching for mail id { sid }...")
# search the mail for a specific id
found = False
for mail in self.db["mail"]:
if mail["id"] == sid:
# tell the sysadmin this
self.log(f"deleting mail id { sid }...")
self.db["mail"].delete(id=mail["id"])
found = True
# if we couldn't find the mail
if not found:
# tell the sysadmin this too
self.log(f"could not find mail id { sid }...")
# then return none because nothing was found
return None
# return at the end
return True
if self.modmail.find_one(id=sid):
self.modmail.delete(id=mail["id"])
return True
# if we could not find this, then tell the sysadmin this too
self.log(f"could not find mail id { sid }...")
# then return none because nothing was found
return None
# shows all mail
def list(self):
@ -176,11 +174,10 @@ class Modmail:
# tell the sysadmin **everything**
self.log(f"listing all mail to someone...")
# this might happen
if not self.db["mail"].all():
if not self.modmail.all():
return None
# the mail
return self.parse(self.db["mail"].all())
return self.parse(self.modmail.all())
# read a specific message
def read_single(self, sid):
@ -190,37 +187,36 @@ class Modmail:
"""
# might be useful for statistics or something
self.log(f"searching for mail id { sid }...")
# find the single message
for mail in self.db["mail"]:
# this too
self.log(f"found mail id { sid }...")
if mail["id"] == sid:
message = mail
return self.parse(message)
# find the message
message = self.modmail.find_one(id=sid)
# check if we found it
if message:
# return the message
return self.parse(message)
# debugging???
self.log(f" could not find mail id { sid }...")
# return None if not found
return None
"""
# tag class for Netux's tags
class tags:
"""
class Tags:
""
class for handling the tags system
"""
""
def __init__(self, serverInfo):
"""
""
initiates the tags class. takes one argument, a serverinfo dict (doc.py #1)
"""
""
# set some variables to save these
self.server = serverInfo # server info, from a YAML file
self.db = dataset.connect(
self.server["dbPath"] # the sqlite3 database object for the server
)
self.log = utils.Logger(
f"db::tags { self.server['dbName'] }",
f"db::tags ({ self.server['dbName'] })",
f"{ self.server['rootDir'] }/db_tags.log",
)
# notify the sysadmin of the connection
@ -265,3 +261,4 @@ class tags:
tags_file.seek(0)
tags_file.write(json.dumps(tags))
tags_file.truncate()
"""

View file

@ -6,6 +6,7 @@
#
# imported modules
import core.utils as utils # import the utility package
import dataset # database things
import discord # dealing with some discord-related things
@ -17,18 +18,20 @@ class Server:
"""
# initiation function
def __init__(self, server_info, server_object):
def __init__(self, server_object):
"""
initiates the server class. takes two arguments, a serverinfo dict (doc.py #1), and a discord.py server object
:param server_info: a serverinfo dict
initiates the server class
:param server_object: a discord.Guild class
"""
# set some variables to save these
self.server_info = server_info # server info, from a YAML file
self.db = dataset.connect(
f"servers/{ server_object.id }/server.db" # the database
)
self.server_settings = utils.KeyValueTable(self.db["settings"])
self.server = server_object # the server object
self.log = utils.Logger(
f"utils::server { self.server_info['name'] }",
f"{ self.server_info['rootDir'] }/server.log",
f"utils::server ({ self.server.id }/{ self.server.name })",
f"servers/{ self.server.id }/server.log",
)
# check if the server object is actually a server object
if type(self.server) != discord.Guild:
@ -40,14 +43,14 @@ class Server:
)
# add a nice log message
self.log(f"initiated server class for { self.server_info['name'] }")
self.log(f"initiated server class for { self.server.id }/{ self.server.name }")
# checking if a member is a mod
def is_mod(self, member):
"""
returns true if user is a mod, false if they aren't
:param member: a discord.Member to check for mod status on
:returns: it returns true if the user is a mod, false if they aren't. it returns none if a type mismatch occured
:returns: it returns true if the user is a mod, false if they aren't. it returns none if a type mismatch occurred
"""
# check if they are an instance of discord.Member
if type(member) != discord.Member:
@ -59,8 +62,8 @@ class Server:
# append the id
user_role_ids.append(str(role.id))
# do the check
return member.id in self.server_info["mods"] or utils.list_overlap(
self.server_info["modRoles"], user_role_ids
return member.id in self.server_settings["mods"] or utils.list_overlap(
self.server_settings["mod_roles"], user_role_ids
)
# get a channel by an identifier
@ -137,7 +140,7 @@ class Server:
return if a member has a role
:param member: a discord role obj or an discord role id. the member id must be of the int type
:param role: a discord member obj or an discord member id. the member id must be of the int type here too
:returns: it returns none if a type mismatch occured, otherwhise it returns true or false if they have it or not
:returns: it returns none if a type mismatch occurred, otherwise it returns true or false if they have it or not
"""
# first, check if the passed role is a discord role object
# or a role id (int)
@ -156,7 +159,7 @@ class Server:
if type(member) != discord.member.Member:
# member id (int)
if type(member) != int:
# you idiot
# i guess it's neither
return None
else:

View file

@ -58,6 +58,7 @@ def whiskerflake():
# add them and return
return epoch_hash + rand_int
# check for list overlap between two lists
def list_overlap(list1, list2):
"""
@ -76,6 +77,62 @@ def list_overlap(list1, list2):
# return if there is overlap
return overlap
# class to make a database table like a key-value store
class KeyValueTable:
"""operate a database table like a key-value store"""
# initiate the KeyValueDB
def __init__(self, table):
"""
initiate the KeyValueDB
:param table: a dataset table
"""
self.table = table
# retrieve a key-value pair from the database
def __getitem__(self, key):
"""
retrieve an item from the key-value store
:param key: the name of the requested item
:return: the requested item
:raises KeyError: it raises KeyError if it cannot be found
"""
# attempt to find the requested item
res = self.table.find_one(key=key)
# check if it returned something
if not res:
# raise IndexError if it cannot be found
raise KeyError(f"unable to find key-value pair with key { key }")
else:
# otherwise return the requested item
return res
# set a key-value pair in the database
def __setitem__(self, key, value):
"""
set an item in the key-value store
:param key: the key to set
:param value: the value for the key to have
"""
# upsert it (insert if it doesn't exist, update it if it does)
self.table.upsert(dict(key=key, value=value), ["key"])
# delete a key-value pair in the database
def __delitem__(self, key):
"""
delete a value in the key-value store if it exists
:param key: the key to delete
"""
# attempt to delete it
self.table.delete(key=key)
# when the key is missing
def __missing__(self, key):
# just return none
return None
"""
def coo(channel, target_user, response):
target_part = f"{ target_user.mention }, " if target_user is not None else ""