26d78795f5ed40a9b438ef2ce18bc61ecebd9334 — Jess Porter 4 months ago c945614
move verification, don't use wait_for because it is globally serial (#2)

2 files changed, 53 insertions(+), 29 deletions(-)

M curite/__init__.py
M curite/httpd.py
M curite/__init__.py => curite/__init__.py +46 -3
@@ 1,9 1,22 @@
from asyncio   import Future
from random    import randint
from irctokens import Line
from re        import compile as re_compile
from typing    import Dict
from irctokens import build, Line
from ircrobots import Bot as BaseBot
from ircrobots import Server as BaseServer
from ircrobots.formatting import strip as format_strip

RE_SUCCESS = re_compile(r"^(?P<account>\S+) has not been verified.$")
RE_ALREADY = re_compile(r"^(?P<account>\S+) is not awaiting verification.$")
RE_INVALID = re_compile(r"^verification failed. invalid key for (?P<account>\S+).$")
RE_ISNTREG = re_compile(r"^(?P<account>\S+) is not registered.$")

class CuriteServer(BaseServer):
    def __init__(self, bot: BaseBot, name: str):
        super().__init__(bot, name)
        self._waiting: Dict[str, Future[bool]] = {}

class Server(BaseServer):
    async def handshake(self):
        nickname = self.params.realname
        while "#" in nickname:

@@ 14,6 27,36 @@ class Server(BaseServer):
        self.params.username = nickname
        await super().handshake()

    def verify(self, account: str, token: str):
        account = self.casefold(account)
        if account in self._waiting:
            future = self._waiting[account]
            future = self._waiting[account] = Future()
            self.send(build("PRIVMSG", ["NickServ", f"VERIFY REGISTER {account} {token}"]))
        return future
    def _verify_result(self, account: str, result: bool):
        if (account := self.casefold(account)) in self._waiting:

    async def line_read(self, line: Line):
        if (line.command == "NOTICE" and
                self.casefold_equals(line.hostmask.nickname, "nickserv")):

            message = format_strip(line.params[1])

            if (p_success := RE_SUCCESS.search(message)) is not None:
                self._verify_result(p_success.group("account"), True)

            elif (p_already := RE_ALREADY.search(message)) is not None:
                self._verify_result(p_already.group("account"), False)

            elif (p_invalid := RE_INVALID.search(message)) is not None:
                self._verify_result(p_invalid.group("account"), False)

            elif (p_isntreg := RE_ISNTREG.search(message)) is not None:
                self._verify_result(p_isntreg.group("account"), False)

    def line_preread(self, line: Line):
        print(f"< {line.format()}")
    def line_presend(self, line: Line):

@@ 21,5 64,5 @@ class Server(BaseServer):

class Bot(BaseBot):
    def create_server(self, name: str):
        return Server(self, name)
        return CuriteServer(self, name)

M curite/httpd.py => curite/httpd.py +7 -26
@@ 1,16 1,14 @@
import asyncio, traceback
from argparse     import ArgumentParser
from asyncio      import StreamReader, StreamWriter
from typing       import List
from typing       import cast, List
from urllib.parse import unquote as url_unquote

from async_timeout import timeout as timeout_

from irctokens import build
from ircrobots import Bot
from ircrobots.matching   import Response, Nick, Folded, Formatless, SELF
from ircrobots.formatting import strip as format_strip

from . import CuriteServer
from .config import Config

async def _headers(

@@ 32,27 30,6 @@ async def _headers(

    return headers

NICKSERV = Nick("NickServ")
async def _verify(
        bot:     Bot,
        account: str,
        token:   str) -> bool:

    success = f"{account} has now been verified."

    server  = list(bot.servers.values())[0]
    async with server.read_lock:
        await server.send(build("PRIVMSG", ["NickServ", f"VERIFY REGISTER {account} {token}"]))
        verify_line = await server.wait_for({
            Response("NOTICE", [SELF, Formatless(Folded(f"{account} is not awaiting verification."))], source=NICKSERV),
            Response("NOTICE", [SELF, Formatless(Folded(f"verification failed. invalid key for {account}."))], source=NICKSERV),
            Response("NOTICE", [SELF, Formatless(Folded(f"{account} is not registered."))], source=NICKSERV),
            Response("NOTICE", [SELF, Formatless(Folded(success))], source=NICKSERV)

        verify_msg = format_strip(verify_line.params[1])
        return server.casefold_equals(success, verify_msg)

async def run(
        bot:    Bot,
        config: Config):

@@ 83,9 60,13 @@ async def run(
        account = url_unquote(path_match.group("account"))
        token   = path_match.group("token")

        if not bot.servers:
        server = cast(CuriteServer, list(bot.servers.values())[0])

            async with timeout_(5):
                verified = await _verify(bot, account, token)
                verified = await server.verify(account, token)
        except asyncio.TimeoutError:
            print("! verify timeout")