2020-04-05 11:48:29 +00:00
|
|
|
from typing import Dict, Iterable, List, Optional
|
2020-04-02 19:16:07 +00:00
|
|
|
from irctokens import build
|
2020-04-01 22:06:41 +00:00
|
|
|
|
2020-04-02 19:16:07 +00:00
|
|
|
from .contexts import ServerContext
|
2020-04-02 19:55:01 +00:00
|
|
|
from .matching import (Response, Numerics, ResponseOr, ParamAny, ParamNot,
|
|
|
|
ParamLiteral)
|
2020-04-02 19:16:07 +00:00
|
|
|
from .interface import ICapability
|
|
|
|
|
|
|
|
class Capability(ICapability):
|
2020-04-01 22:06:41 +00:00
|
|
|
def __init__(self,
|
|
|
|
ratified_name: Optional[str],
|
|
|
|
draft_name: Optional[str]=None,
|
|
|
|
alias: Optional[str]=None,
|
|
|
|
depends_on: List[str]=[]):
|
|
|
|
self.name = ratified_name
|
|
|
|
self.draft = draft_name
|
|
|
|
self.alias = alias or ratified_name
|
|
|
|
self.depends_on = depends_on.copy()
|
|
|
|
|
|
|
|
self._caps = set((ratified_name, draft_name))
|
|
|
|
|
|
|
|
def available(self, capabilities: Iterable[str]
|
|
|
|
) -> Optional[str]:
|
|
|
|
match = list(set(capabilities)&self._caps)
|
|
|
|
return match[0] if match else None
|
|
|
|
|
|
|
|
def match(self, capability: str) -> Optional[str]:
|
|
|
|
cap = list(set([capability])&self._caps)
|
|
|
|
return cap[0] if cap else None
|
|
|
|
|
|
|
|
def copy(self):
|
|
|
|
return Capability(
|
|
|
|
self.name,
|
|
|
|
self.draft,
|
|
|
|
alias=self.alias,
|
|
|
|
depends_on=self.depends_on[:])
|
|
|
|
|
2020-04-02 15:59:02 +00:00
|
|
|
CAP_SASL = Capability("sasl")
|
2020-04-02 19:16:07 +00:00
|
|
|
CAPS: List[ICapability] = [
|
2020-04-01 22:06:41 +00:00
|
|
|
Capability("multi-prefix"),
|
|
|
|
Capability("chghost"),
|
|
|
|
Capability("away-notify"),
|
|
|
|
Capability("userhost-in-names"),
|
|
|
|
|
|
|
|
Capability("invite-notify"),
|
|
|
|
Capability("account-tag"),
|
|
|
|
Capability("account-notify"),
|
|
|
|
Capability("extended-join"),
|
|
|
|
|
|
|
|
Capability("message-tags", "draft/message-tags-0.2"),
|
|
|
|
Capability("cap-notify"),
|
|
|
|
Capability("batch"),
|
|
|
|
|
|
|
|
Capability(None, "draft/rename", alias="rename"),
|
|
|
|
Capability("setname", "draft/setname")
|
|
|
|
]
|
2020-04-02 19:16:07 +00:00
|
|
|
|
|
|
|
class CAPContext(ServerContext):
|
2020-04-05 11:48:29 +00:00
|
|
|
async def on_ls(self, tokens: Dict[str, str]):
|
|
|
|
caps = list(self.server.desired_caps)+CAPS
|
|
|
|
|
|
|
|
if (not self.server.params.sasl is None and
|
|
|
|
not CAP_SASL in caps):
|
|
|
|
caps.append(CAP_SASL)
|
|
|
|
|
|
|
|
matched = (c.available(tokens) for c in caps)
|
|
|
|
cap_names = [name for name in matched if not name is None]
|
|
|
|
|
|
|
|
if cap_names:
|
|
|
|
await self.server.send(build("CAP", ["REQ", " ".join(cap_names)]))
|
|
|
|
|
|
|
|
while cap_names:
|
|
|
|
line = await self.server.wait_for(ResponseOr(
|
|
|
|
Response("CAP", [ParamAny(), ParamLiteral("ACK")]),
|
|
|
|
Response("CAP", [ParamAny(), ParamLiteral("NAK")])
|
|
|
|
))
|
|
|
|
|
|
|
|
current_caps = line.params[2].split(" ")
|
|
|
|
for cap in current_caps:
|
|
|
|
if cap in cap_names:
|
|
|
|
cap_names.remove(cap)
|
|
|
|
if (self.server.cap_agreed(CAP_SASL) and
|
|
|
|
not self.server.params.sasl is None):
|
|
|
|
await self.server.sasl_auth(self.server.params.sasl)
|
|
|
|
|
2020-04-02 19:16:07 +00:00
|
|
|
async def handshake(self) -> bool:
|
|
|
|
# improve this by being able to wait_for Emit objects
|
2020-04-02 19:55:01 +00:00
|
|
|
line = await self.server.wait_for(ResponseOr(
|
|
|
|
Response(
|
|
|
|
"CAP",
|
|
|
|
[ParamAny(), ParamLiteral("LS"), ParamNot(ParamLiteral("*"))]
|
|
|
|
),
|
|
|
|
Numerics(["RPL_WELCOME"])
|
|
|
|
))
|
2020-04-02 19:16:07 +00:00
|
|
|
|
|
|
|
if line.command == "CAP":
|
2020-04-05 11:48:29 +00:00
|
|
|
await self.on_ls(self.server.available_caps)
|
2020-04-02 19:16:07 +00:00
|
|
|
await self.server.send(build("CAP", ["END"]))
|
|
|
|
return True
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|