M beryllia/database/__init__.py => beryllia/database/__init__.py +4 -0
@@ 13,6 13,8 @@ from .preference import PreferenceTable
from .registration import RegistrationTable
from .email_resolve import EmailResolveTable
+from .account_freeze import AccountFreezeTable
+from .freeze_tag import FreezeTagTable
from ..normalise import SearchNormaliser
@@ 32,6 34,8 @@ class Database(object):
self.preference = PreferenceTable(pool, normaliser)
self.registration = RegistrationTable(pool, normaliser)
self.email_resolve = EmailResolveTable(pool, normaliser)
+ self.account_freeze = AccountFreezeTable(pool, normaliser)
+ self.freeze_tag = FreezeTagTable(pool, normaliser)
@staticmethod
async def connect(
A beryllia/database/account_freeze.py => beryllia/database/account_freeze.py +14 -0
@@ 0,0 1,14 @@
+from .common import Table
+
+
+class AccountFreezeTable(Table):
+ async def add(self, account: str, soper: str, reason: str) -> int:
+
+ query = """
+ INSERT INTO account_freeze (account, soper, reason, ts)
+ VALUES ($1, $2, $3, NOW()::TIMESTAMP)
+ RETURNING id
+ """
+
+ async with self.pool.acquire() as conn:
+ return await conn.fetchval(query, account, soper, reason)
A beryllia/database/freeze_tag.py => beryllia/database/freeze_tag.py +30 -0
@@ 0,0 1,30 @@
+from .common import Table
+from ..normalise import SearchType
+
+
+class FreezeTagTable(Table):
+ async def add(self, freeze_id: int, tag: str, soper: str) -> None:
+
+ query = """
+ INSERT INTO freeze_tag (freeze_id, tag, search_tag, soper, ts)
+ VALUES ($1, $2, $3, $4, NOW()::TIMESTAMP)
+ """
+
+ async with self.pool.acquire() as conn:
+ await conn.execute(
+ query, freeze_id, tag, str(self.to_search(tag, SearchType.TAG)), soper
+ )
+
+ async def exists(self, freeze_id: int, tag: str) -> bool:
+ query = """
+ SELECT 1 FROM freeze_tag
+ WHERE freeze_id = $1
+ AND search_tag = $2
+ """
+
+ async with self.pool.acquire() as conn:
+ return bool(
+ await conn.fetchval(
+ query, freeze_id, str(self.to_search(tag, SearchType.TAG))
+ )
+ )
M beryllia/parse/common.py => beryllia/parse/common.py +3 -0
@@ 1,5 1,8 @@
+from re import compile as re_compile
from irctokens import Line
+RE_EMBEDDEDTAG = re_compile(r"%(\S+)")
+
class IRCParser:
async def handle(self, line: Line) -> None:
M beryllia/parse/nickserv.py => beryllia/parse/nickserv.py +25 -1
@@ 3,7 3,7 @@ from typing import Any, Awaitable, Callable, Dict, List, Optional
from irctokens import Line
-from .common import IRCParser
+from .common import IRCParser, RE_EMBEDDEDTAG
from ..database import Database
from ..util import recursive_mx_resolve
@@ 124,3 124,27 @@ class NickServParser(IRCParser):
registration_id = self._registration_ids[account]
await self._database.registration.verify(registration_id)
+
+ @_handler("FREEZE:ON")
+ async def _handle_FREEZE_ON(
+ self, soper: str, soper_account: Optional[str], args: str
+ ) -> None:
+
+ match = re_search(r"(?P<account>\S+) \(reason: (?P<reason>.*)\)$", args)
+ if match is None:
+ return
+
+ soper = soper_account or soper
+
+ account = match.group("account")
+ reason = match.group("reason")
+
+ freeze_id = await self._database.account_freeze.add(account, soper, reason)
+
+ tags = list(RE_EMBEDDEDTAG.finditer(reason))
+ for tag_match in tags:
+ tag = tag_match.group(1)
+ if await self._database.freeze_tag.exists(freeze_id, tag):
+ continue
+
+ await self._database.freeze_tag.add(freeze_id, tag, soper)
M beryllia/parse/snote.py => beryllia/parse/snote.py +2 -4
@@ 16,15 16,13 @@ from typing import (
from irctokens import Line
-from .common import IRCParser
+from .common import IRCParser, RE_EMBEDDEDTAG
from ..database import Database
from ..database.cliconn import Cliconn
_TYPE_HANDLER = Callable[[Any, str, Match], Awaitable[None]]
_HANDLERS: List[Tuple[Pattern, _TYPE_HANDLER]] = []
-RE_KLINETAG = re_compile(r"%(\S+)")
-
def _handler(pattern: str) -> Callable[[_TYPE_HANDLER], _TYPE_HANDLER]:
def _inner(func: _TYPE_HANDLER) -> _TYPE_HANDLER:
@@ 269,7 267,7 @@ class SnoteParser(IRCParser):
# TODO: just pass a KLine object to _kline_new
await self._kline_new(kline_id)
- tags = list(RE_KLINETAG.finditer(reason))
+ tags = list(RE_EMBEDDEDTAG.finditer(reason))
for tag_match in tags:
tag = tag_match.group(1)
if await self._database.kline_tag.exists(kline_id, tag):
M make-database.sql => make-database.sql +17 -0
@@ 156,6 156,23 @@ CREATE TABLE email_resolve (
record VARCHAR(256) NOT NULL
);
+CREATE TABLE account_freeze (
+ id SERIAL PRIMARY KEY,
+ account VARCHAR(16) NOT NULL,
+ soper VARCHAR(16) NOT NULL,
+ reason VARCHAR(256) NOT NULL,
+ ts TIMESTAMP NOT NULL
+);
+
+CREATE TABLE freeze_tag (
+ freeze_id INTEGER NOT NULL REFERENCES account_freeze (id) ON DELETE CASCADE,
+ tag VARCHAR(32) NOT NULL,
+ search_tag VARCHAR(32) NOT NULL,
+ soper VARCHAR(16) NOT NULL,
+ ts TIMESTAMP NOT NULL,
+ PRIMARY KEY (freeze_id, search_tag)
+);
+
CREATE TABLE statsp (
oper VARCHAR(16) NOT NULL,
mask VARCHAR(92) NOT NULL,