update analytics, sql and math parser

parent 7a855e14
......@@ -20,16 +20,13 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import functools
import platform
import subprocess
import traceback
import discord
import psutil
from datetime import datetime
from discord.ext import commands
from sqlalchemy import __version__ as sqlalchemy_version
#from .utils import sql
from .utils.colors import paint
......@@ -57,6 +54,18 @@ class Analytics:
else:
self.bot.socket_log[t_type] = 1
# Commands
async def log_command_use(self, ctx):
await self.bot.db.create_command_report(ctx.author, ctx.message, ctx.command, ctx.invoked_with)
# Message created
async def log_message(self, message):
await self.bot.db.create_message(message)
# Message updated
async def log_message_change(self, message, edited):
await self.bot.db.edit_message(message, message.edited_at if edited else datetime.now(), edited)
# Listener functions
# Socket event arrived
......@@ -65,50 +74,53 @@ class Analytics:
self.log_socket_data(payload)
# Command was triggered
# async def on_command(self, ctx):
# message = ctx.message
# channel = ctx.channel
# author = ctx.author
# destination = None
async def on_command(self, ctx):
message = ctx.message
channel = ctx.channel
author = ctx.author
destination = None
# if not hasattr(channel, "guild"):
# destination = "Private Message"
if not hasattr(channel, "guild"):
destination = "Private Message"
# else:
# destination = f"[{ctx.guild.name} #{channel.name}]"
else:
destination = f"[{ctx.guild.name} #{channel.name}]"
# log.info(f"{destination}: {author.name}: {message.clean_content}")
log.info(f"{destination}: {author.name}: {message.clean_content}")
# await self.log_command_use(ctx)
await self.log_command_use(ctx)
# Message arrived
# async def on_message(self, message):
# channel = message.channel
# author = message.author
async def on_message(self, message):
channel = message.channel
author = message.author
# if author.bot or (not self.bot.is_ready()):
# return
if author.bot or (not self.bot.is_ready()):
return
# if hasattr(author, "display_name"):
# await self.log_message(message)
if hasattr(author, "display_name"):
await self.log_message(message)
# Message deleted
# async def on_message_delete(self, message):
# channel = message.channel
# author = message.author
# if hasattr(channel, "guild") and hasattr(author, "display_name"):
# await self.log_message_change(message, deleted=True)
async def on_message_delete(self, message):
channel = message.channel
author = message.author
if hasattr(channel, "guild") and hasattr(author, "display_name"):
await self.log_message_change(message, edited=False)
# Messaged edited
# async def on_message_edit(self, old_message, new_message):
# channel = new_message.channel
# author = new_message.author
# if old_message.content != new_message.content:
# if hasattr(channel, "guild") and hasattr(author, "display_name"):
# await self.log_message_change(new_message, deleted=False)
async def on_message_edit(self, old_message, new_message):
channel = new_message.channel
author = new_message.author
if old_message.content != new_message.content:
if hasattr(channel, "guild") and hasattr(author, "display_name"):
await self.log_message_change(new_message, edited=True)
# Coommand tossed an error
async def on_command_error(self, ctx, error):
if ctx.command is not None:
await self.bot.db.edit_command_report(ctx.message, True)
if isinstance(error, commands.NoPrivateMessage):
await ctx.send("\N{WARNING SIGN} You cannot use that command in a private channel")
......
......@@ -47,6 +47,10 @@ class InvalidBinaryOpError(MathParserError):
def __init__(self, node):
super().__init__(f"Operation `{type(node.op).__name__}` is unsupported on `{type(node.left).__name__}` and `{type(node.right).__name__}`")
class InvalidCompareOpError(MathParserError):
def __init__(self, op):
super().__init__(f"Operation `{type(op).__name__}` ({op}) is not a valid comparison operator")
class InvalidUnaryOpError(MathParserError):
def __init__(self, node):
super().__init__(f"Operation `{type(node.op).__name__}` is unsupported on `{type(node.operand).__name__}`")
......@@ -87,7 +91,14 @@ class MathParser:
ast.LShift: op.lshift,
ast.RShift: op.rshift,
ast.USub: op.neg
ast.USub: op.neg,
ast.Eq: op.eq,
ast.NotEq: op.ne,
ast.Lt: op.lt,
ast.LtE: op.le,
ast.Gt: op.gt,
ast.GtE: op.ge
}
# Allowable functions
......@@ -160,8 +171,13 @@ class MathParser:
def run(self, text):
return self.__call__(text)
# List chunker for comparison chains
def _sequential_chunks(self, arr, n=2):
for i in range(0, len(arr)):
yield arr[i:i + n]
# Actual parsing here
def __parse(self, node):
def _parse(self, node):
log.debug(f"Parsing {ast.dump(node)}")
# We have a plain number
......@@ -176,7 +192,7 @@ class MathParser:
left_op = node.left
right_op = node.right
return self.operators[op_name](self._MathParser__parse(left_op), self._MathParser__parse(right_op))
return self.operators[op_name](self._parse(left_op), self._parse(right_op))
else:
raise InvalidBinaryOpError(node)
......@@ -186,12 +202,12 @@ class MathParser:
op_name = type(node.op)
if op_name in self.operators:
return self.operators[op_name](self._MathParser__parse(node.operand))
return self.operators[op_name](self._parse(node.operand))
else:
raise InvalidUnaryOpError(node)
# loading a name
# Loading a name
elif isinstance(node, ast.Name):
var_name = node.id.lower()
......@@ -212,7 +228,7 @@ class MathParser:
# check/validate positional args
for arg in node.args:
if isinstance(arg, (ast.Name, ast.Num, ast.BinOp, ast.UnaryOp, ast.Call)):
args.append(self._MathParser__parse(arg))
args.append(self._parse(arg))
# check/validate keyword args and run
if len(node.keywords) == 0:
......@@ -229,6 +245,28 @@ class MathParser:
else:
raise MathParserError("Function calls cannot have keyword arguments")
# Comparison op
elif isinstance(node, ast.Compare):
ops_list = []
for op in node.ops:
op_name = type(op)
if op_name in self.operators:
ops_list.append(self.operators[op_name])
else:
raise InvalidCompareOpError(op)
comparators = [self._parse(node.left)] + [self._parse(expr) for expr in node.comparators]
if len(comparators) != len(ops_list) + 1:
raise ParserError("Unbalanced operators and comparators")
# [ [<lt>, [n_0, n_1]], [<gt>, [n_1, n_2]] ]
result = all(data[0](data[1][0], data[1][1]) for data in zip(ops_list, list(self._sequential_chunks(comparators))[:-1]))
return result
else:
raise IllegalTokenError(node)
......@@ -240,4 +278,4 @@ class MathParser:
except Exception as e:
raise ParserError(str(e)) from e
return self._MathParser__parse(expr)
\ No newline at end of file
return self._parse(expr)
\ No newline at end of file
......@@ -60,6 +60,22 @@ CREATE TABLE IF NOT EXISTS chat_logs (
);
"""
MESSAGE_UPDATES_TABLE = """
CREATE TABLE IF NOT EXISTS chat_updates (
pk SERIAL NOT NULL,
timestamp TIMESTAMP,
edit BOOLEAN,
message_id BIGINT,
content VARCHAR(2000),
CONSTRAINT chat_updates_pk PRIMARY KEY (pk),
UNIQUE (pk),
FOREIGN KEY(message_id) REFERENCES chat_logs (id)
);
"""
TAGS_TABLE = """
CREATE TABLE IF NOT EXISTS tags (
name VARCHAR(50) NOT NULL,
......@@ -181,7 +197,7 @@ class SQL:
async def _setup_tables(self):
await asyncio.sleep(1)
async with self.pool.acquire() as conn:
for query in (USERS_TABLE, MESSAGES_TABLE, TAGS_TABLE, PREFIX_TABLE, PERMISSION_TABLE,BLACKLIST_TABLE, WHITELIST_TABLE, ERRORS_TABLE, STATS_TABLE):
for query in (USERS_TABLE, MESSAGES_TABLE, MESSAGE_UPDATES_TABLE, TAGS_TABLE, PREFIX_TABLE, PERMISSION_TABLE,BLACKLIST_TABLE, WHITELIST_TABLE, ERRORS_TABLE, STATS_TABLE):
await conn.execute(query)
async def close(self):
......@@ -196,24 +212,41 @@ class SQL:
async with self.pool.acquire() as conn:
return await conn.execute("UPDATE users SET name = $1, discrim = $2 WHERE id = $3;", user.name, user.discriminator, user.id)
async def check_user(self, conn, user):
test_user = await conn.fetchrow("SELECT 1 as test FROM users WHERE id = $1;", user.id);
async def check_user(self, user):
async with self.pool.acquire() as conn:
test_user = await conn.fetchrow("SELECT 1 as test FROM users WHERE id = $1;", user.id);
if test_user is None:
await self.create_user(user)
if test_user is None:
await self.create_user(user)
# Messages
async def create_message(self, message):
async with self.pool.acquire() as conn:
await self.check_user(conn, message.author)
await self.check_user(message.author)
return await conn.execute("INSERT INTO chat_logs(id, timestamp, author_id, channel_id, guild_id, content) VALUES($1, $2, $3, $4, $5, $6);",
message.id, message.created_at, message.author.id, message.channel.id, message.guild.id, message.content)
async def edit_message(self, message, timestamp, edited):
async with self.pool.acquire() as conn:
await self.check_message(message)
return await conn.execute("INSERT INTO chat_updates(timestamp, edit, message_id, content) VALUES($1, $2, $3, $4);",
timestamp, edited, message.id, message.content if edited else "")
async def check_message(self, message):
async with self.pool.acquire() as conn:
test_message = await conn.fetchrow("SELECT 1 as test FROM chat_logs WHERE id = $1;", message.id);
if test_message is None:
await self.create_message(message)
# Tags
async def create_tag(self, author, timestamp, name, content):
async with self.pool.acquire() as conn:
await self.check_user(conn, author)
await self.check_user(author)
return await conn.execute("INSERT INTO tags(name, author_id, content, uses, timestamp, data) VALUES($1, $2, $3, $4, $5, $6);",
name, author.id, content, 0, timestamp, "{}")
......@@ -237,18 +270,24 @@ class SQL:
return await conn.fetch("SELECT prefix FROM prefixes WHERE id = $1 AND personal = $2;", item_id, personal)
# Errors
async def create_error_report(self, report):
async with self.pool.acquire() as conn:
return await conn.execute("INSERT INTO logged_errors(level, module, function, filename, line, message, timestamp) VALUES($1, $2, $3, $4, $5, $6, $7);",
report.levelname, report.module, report.funcName, report.filename, report.lineno, report.msg, datetime.fromtimestamp(report.created))
# Command stats
async def create_command_report(self, user, message, command):
async def create_command_report(self, user, message, command, invoked_with):
async with self.pool.acquire() as conn:
await self.check_user(user)
return await conn.execute("INSERT INTO command_stats(message_id, command_name, user_id, args, errored) VALUES($1, $2, $3, $4, $5);",
message.id, command.qualified_name, user.id, message.clean_context.split(command.invoked_with)[1].strip(), False)
message.id, command.qualified_name, user.id, message.clean_content.split(invoked_with)[1].strip(), False)
# Command errors
async def edit_command_report(self, message, errored):
async with self.pool.acquire() as conn:
return await conn.execute("UPDATE command_stats SET errored = $1 WHERE message_id = $2;",
errored, message.id)
toml
git+https://github.com/Rapptz/discord.py.git@rewrite#egg=discord.py[voice]
discord.py
asyncpg
uvloop
Pillow
......
......@@ -256,8 +256,6 @@ class SnakeBot(commands.Bot):
prefixes += [prefix["prefix"] for prefix in guild_prefixes]
print(prefixes)
return prefixes
# Post a reaction indicating command status
......@@ -320,7 +318,7 @@ class SnakeBot(commands.Bot):
if not reaction.me and not reaction.custom_emoji:
message = reaction.message
if reaction.emoji == "\N{CLOCKWISE RIGHTWARDS AND LEFTWARDS OPEN CIRCLE ARROWS}":
if reaction.emoji == "\N{CLOCKWISE RIGHTWARDS AND LEFTWARDS OPEN CIRCLE ARROWS}" and message.author == user:
await self.on_message(message)
elif reaction.emoji == "\N{WASTEBASKET}" and message.author == message.guild.me:
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment