diff --git a/beryllia/database/__init__.py b/beryllia/database/__init__.py index 01e03b1..c73fa8d 100644 --- a/beryllia/database/__init__.py +++ b/beryllia/database/__init__.py @@ -13,6 +13,8 @@ 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 @@ def __init__(self, pool: asyncpg.Pool, normaliser: SearchNormaliser): 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( diff --git a/beryllia/database/account_freeze.py b/beryllia/database/account_freeze.py new file mode 100644 index 0000000..1aed507 --- /dev/null +++ b/beryllia/database/account_freeze.py @@ -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) diff --git a/beryllia/database/freeze_tag.py b/beryllia/database/freeze_tag.py new file mode 100644 index 0000000..1f40d01 --- /dev/null +++ b/beryllia/database/freeze_tag.py @@ -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)) + ) + ) diff --git a/beryllia/parse/common.py b/beryllia/parse/common.py index cad9912..3db1a3b 100644 --- a/beryllia/parse/common.py +++ b/beryllia/parse/common.py @@ -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: diff --git a/beryllia/parse/nickserv.py b/beryllia/parse/nickserv.py index 10f3731..e2ee810 100644 --- a/beryllia/parse/nickserv.py +++ b/beryllia/parse/nickserv.py @@ -3,7 +3,7 @@ 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 @@ async def _handle_VERIFY_REGISTER( 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\S+) \(reason: (?P.*)\)$", 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) diff --git a/beryllia/parse/snote.py b/beryllia/parse/snote.py index a4f4097..df8454b 100644 --- a/beryllia/parse/snote.py +++ b/beryllia/parse/snote.py @@ -16,15 +16,13 @@ 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 @@ async def _handle_klineadd(self, server: str, match: Match) -> None: # 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): diff --git a/make-database.sql b/make-database.sql index f5450f9..c3ab759 100644 --- a/make-database.sql +++ b/make-database.sql @@ -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,