Compare commits

...

4 Commits

10 changed files with 307 additions and 10 deletions

View File

@@ -259,6 +259,31 @@ CREATE TABLE subscriber_events (
);
CREATE VIEW event_details AS
SELECT
events.event_id AS event_id,
events.user_id AS user_id,
events.document_id AS document_id,
events.user_name AS user_name,
events.event_type AS event_type,
events.occurred_at AS occurred_at,
events.created_at AS created_at,
plain_events.message AS plain_message,
raid_events.visitor_count AS raid_visitor_count,
cheer_events.amount AS cheer_amount,
cheer_events.cheer_type AS cheer_type,
cheer_events.message AS cheer_message,
subscriber_events.subscribed_length AS subscriber_length,
subscriber_events.tier AS subscriber_tier,
subscriber_events.message AS subscriber_message,
FROM
events
LEFT JOIN plain_events USING (event_id)
LEFT JOIN raid_events USING (event_id)
LEFT JOIN cheer_events USING (event_id)
LEFT JOIN subscriber_events USING (event_id)
ORDER BY events.occurred_at ASC;
-- }}}
-- Specimens {{{

View File

@@ -76,6 +76,7 @@ async def main():
config=conf,
initial_extensions=[
'core',
'twitch',
'modules',
],
web_client=session,

View File

@@ -47,8 +47,8 @@ class Connector:
return AsyncConnectionPool(
self._conn_args,
open=False,
min_size=4,
max_size=8,
min_size=1,
max_size=4,
configure=self._setup_connection,
kwargs=self._conn_kwargs
)

View File

@@ -3,7 +3,7 @@ from typing import Optional
from psycopg import AsyncCursor, sql
from psycopg.abc import Query, Params
from psycopg._encodings import pgconn_encoding
from psycopg._encodings import conn_encoding
logger = logging.getLogger(__name__)
@@ -15,7 +15,7 @@ class AsyncLoggingCursor(AsyncCursor):
elif isinstance(query, (sql.SQL, sql.Composed)):
msg = query.as_string(self)
elif isinstance(query, bytes):
msg = query.decode(pgconn_encoding(self._conn.pgconn), 'replace')
msg = query.decode(conn_encoding(self._conn.pgconn), 'replace')
else:
msg = repr(query)
return msg

273
src/datamodels.py Normal file
View File

@@ -0,0 +1,273 @@
from enum import Enum
from data import Registry, RowModel, Table, RegisterEnum
from data.columns import Integer, String, Timestamp, Column
class EventType(Enum):
SUBSCRIBER = 'subscriber',
RAID = 'raid',
CHEER = 'cheer',
PLAIN = 'plain',
class DataModel(Registry):
_EventType = RegisterEnum(EventType, 'EventType')
class UserPreferences(RowModel):
"""
Schema
------
CREATE TABLE user_preferences (
profileid INTEGER PRIMARY KEY REFERENCES user_profiles (profileid) ON DELETE CASCADE,
twitch_name TEXT,
preferences TEXT
);
"""
_tablename_ = 'user_preferences'
_cache_ = {}
profileid = Integer(primary=True)
twitch_name = String()
preferences = String()
class Dreamer(RowModel):
"""
Schema
------
CREATE VIEW dreamers AS
SELECT
user_profiles.profileid AS user_id,
user_preferences.twitch_name AS name,
profiles_twitch.userid AS twitch_id,
user_preferences.preferences AS preferences,
user_profiles.created_at AS created_at
FROM
user_profiles
LEFT JOIN profiles_twitch USING (profileid)
LEFT JOIN user_preferences USING (profileid);
"""
_tablename_ = ''
_readonly_ = True
profileid = Integer(primary=True)
name = String()
twitch_id = Integer()
preferences = String()
created_at = Timestamp()
class Transaction(RowModel):
"""
Schema
------
CREATE TABLE user_wallet (
transaction_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES user_profiles (profileid) ON DELETE CASCADE,
amount INTEGER NOT NULL,
description TEXT NOT NULL,
reference TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
"""
_tablename_ = 'user_wallet'
_cache_ = {}
_immutable_ = True
transaction_id = Integer(primary=True)
user_id = Integer()
amount = Integer()
description = String()
reference = String()
created_at = Timestamp()
class StampType(RowModel):
"""
Schema
------
CREATE TABLE stamp_types (
stamp_type_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
stamp_type_name TEXT UNIQUE NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
"""
_tablename_ = 'stamp_types'
_cache_ = {}
stamp_type_id = Integer(primary=True)
stamp_type_name = String()
created_at = Timestamp()
class Document(RowModel):
"""
Schema
------
CREATE TABLE documents (
document_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
document_data VARCHAR NOT NULL,
seal INTEGER NOT NULL,
metadata TEXT
);
"""
_tablename_ = 'documents'
_cache_ = {}
document_id = Integer(primary=True)
document_data = Column()
seal = Integer()
metadata = String()
class DocumentStamp(RowModel):
"""
Schema
------
CREATE TABLE document_stamps (
stamp_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
document_id INTEGER NOT NULL REFERENCES documents (document_id) ON DELETE CASCADE,
stamp_type INTEGER NOT NULL REFERENCES stamp_types (stamp_type_id) ON DELETE CASCADE,
position_x INTEGER NOT NULL,
position_y INTEGER NOT NULL,
rotation REAL NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
"""
_tablename_ = 'document_stamps'
_cache_ = {}
stamp_id = Integer(primary=True)
document_id = Integer()
stamp_type = Integer()
position_x = Integer()
position_y = Integer()
rotation: Column[float] = Column()
created_at = Timestamp()
class Events(RowModel):
"""
Schema
------
CREATE TABLE events (
event_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES user_profiles (profileid) ON DELETE CASCADE,
document_id INTEGER REFERENCES documents (document_id) ON DELETE SET NULL,
user_name TEXT,
event_type EventType NOT NULL,
occurred_at TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (event_id, event_type)
);
"""
_tablename_ = 'events'
_cache_ = {}
event_id = Integer(primary=True)
user_id = Integer()
document_id = Integer()
user_name = String()
event_type: Column[EventType] = Column()
occured_at = Timestamp()
created_at = Timestamp()
plain_events = Table('plain_events')
raid_events = Table('raid_events')
cheer_events = Table('cheer_events')
subscriber_events = Table('subscriber_events')
class EventDetails(RowModel):
"""
Schema
------
CREATE TABLE plain_events (
event_id integer PRIMARY KEY,
event_type EventType NOT NULL DEFAULT 'plain' CHECK (event_type = 'plain'),
message TEXT NOT NULL,
FOREIGN KEY (event_id, event_type) REFERENCES events (event_id, event_type)
);
CREATE TABLE raid_events (
event_id integer PRIMARY KEY,
event_type EventType NOT NULL DEFAULT 'raid' CHECK (event_type = 'raid'),
visitor_count INTEGER NOT NULL,
FOREIGN KEY (event_id, event_type) REFERENCES events (event_id, event_type)
);
CREATE TABLE cheer_events (
event_id integer PRIMARY KEY,
event_type EventType NOT NULL DEFAULT 'cheer' CHECK (event_type = 'cheer'),
amount INTEGER NOT NULL,
cheer_type TEXT,
message TEXT,
FOREIGN KEY (event_id, event_type) REFERENCES events (event_id, event_type)
);
CREATE TABLE subscriber_events (
event_id integer PRIMARY KEY,
event_type EventType NOT NULL DEFAULT 'subscriber' CHECK (event_type = 'subscriber'),
subscribed_length INTEGER NOT NULL,
tier INTEGER NOT NULL,
message TEXT,
FOREIGN KEY (event_id, event_type) REFERENCES events (event_id, event_type)
);
CREATE VIEW event_details AS
SELECT
events.event_id AS event_id,
events.user_id AS user_id,
events.document_id AS document_id,
events.user_name AS user_name,
events.event_type AS event_type,
events.occurred_at AS occurred_at,
events.created_at AS created_at,
plain_events.message AS plain_message,
raid_events.visitor_count AS raid_visitor_count,
cheer_events.amount AS cheer_amount,
cheer_events.cheer_type AS cheer_type,
cheer_events.message AS cheer_message,
subscriber_events.subscribed_length AS subscriber_length,
subscriber_events.tier AS subscriber_tier,
subscriber_events.message AS subscriber_message,
FROM
events
LEFT JOIN plain_events USING (event_id)
LEFT JOIN raid_events USING (event_id)
LEFT JOIN cheer_events USING (event_id)
LEFT JOIN subscriber_events USING (event_id)
ORDER BY events.occurred_at ASC;
"""
_tablename_ = 'event_details'
_readonly_ = True
event_id = Integer(primary=True)
user_id = Integer()
document_id = Integer()
user_name = String()
event_type: Column[EventType] = Column()
occurred_at = Timestamp()
created_at = Timestamp()
plain_message = String()
raid_visitor_count = Integer()
cheer_amount = Integer()
cheer_type = String()
cheer_message = String()
subscriber_length = Integer()
subscriber_tier = Integer()
subscriber_message = String()
class Specimen(RowModel):
"""
Schema
------
CREATE TABLE user_specimens (
specimen_id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
owner_id INTEGER NOT NULL REFERENCES user_profiles (profileid) ON DELETE CASCADE,
born_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
forgotten_at TIMESTAMPTZ
);
"""
_tablename_ = 'user_specimens'
_cache_ = {}
specimen_id = Integer(primary=True)
owner_id = Integer(primary=True)
born_at = Timestamp()
forgotten_at = Timestamp()

View File

@@ -203,7 +203,7 @@ class LionBot(Bot):
# TODO: Some of these could have more user-feedback
logger.debug(f"Handling command error for {ctx}: {exception}")
if isinstance(ctx.command, HybridCommand) and ctx.command.app_command:
cmd_str = ctx.command.app_command.to_dict()
cmd_str = ctx.command.app_command.to_dict(self.tree)
else:
cmd_str = str(ctx.command)
try:

View File

@@ -133,7 +133,7 @@ class LionTree(CommandTree):
return
set_logging_context(action=f"Run {command.qualified_name}")
logger.debug(f"Running command '{command.qualified_name}': {command.to_dict()}")
logger.debug(f"Running command '{command.qualified_name}': {command.to_dict(self)}")
try:
await command._invoke_with_namespace(interaction, namespace)
except AppCommandError as e:

View File

@@ -1,6 +1,7 @@
this_package = 'modules'
active = [
'.profiles',
'.sysadmin',
]

View File

@@ -9,8 +9,6 @@ from discord.ext import commands as cmds
from twitchAPI.helper import first
from twitchAPI.type import AuthScope
import twitchio
from twitchio.ext import commands
from twitchio import User
from twitchAPI.object.api import TwitchUser

View File

@@ -7,9 +7,8 @@ import discord
from discord.ext import commands as cmds
from twitchAPI.oauth import UserAuthenticator
from twitchAPI.twitch import AuthType, Twitch
from twitchAPI.twitch import Twitch
from twitchAPI.type import AuthScope
import twitchio
from twitchio.ext import commands