Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 40 additions & 35 deletions examples/factoids.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,36 @@
import asyncio, re
from argparse import ArgumentParser
from typing import Dict, List, Optional
import asyncio
import re
from argparse import ArgumentParser
from typing import Dict, List, Optional

from irctokens import build, Line
from ircrobots import Bot as BaseBot
from ircrobots import Server as BaseServer
from irctokens import Line, build

from ircrobots import Bot as BaseBot
from ircrobots import ConnectionParams
from ircrobots import Server as BaseServer

TRIGGER = "!"


def _delims(s: str, delim: str):
s_copy = list(s)
while s_copy:
char = s_copy.pop(0)
if char == delim:
if not s_copy:
yield len(s)-(len(s_copy)+1)
yield len(s) - (len(s_copy) + 1)
elif not s_copy.pop(0) == delim:
yield len(s)-(len(s_copy)+2)
yield len(s) - (len(s_copy) + 2)


def _sed(sed: str, s: str) -> Optional[str]:
if len(sed) > 1:
delim = sed[1]
last = 0
delim = sed[1]
last = 0
parts: List[str] = []
for i in _delims(sed, delim):
parts.append(sed[last:i])
last = i+1
last = i + 1
if len(parts) == 4:
break
if last < (len(sed)):
Expand All @@ -36,10 +40,10 @@ def _sed(sed: str, s: str) -> Optional[str]:
flags_s = (args or [""])[0]

flags = re.I if "i" in flags_s else 0
count = 0 if "g" in flags_s else 1
count = 0 if "g" in flags_s else 1

for i in reversed(list(_delims(replace, "&"))):
replace = replace[:i] + "\\g<0>" + replace[i+1:]
replace = replace[:i] + "\\g<0>" + replace[i + 1 :]

try:
compiled = re.compile(pattern, flags)
Expand All @@ -49,18 +53,22 @@ def _sed(sed: str, s: str) -> Optional[str]:
else:
return None


class Database:
def __init__(self):
self._settings: Dict[str, str] = {}

async def get(self, context: str, setting: str) -> Optional[str]:
return self._settings.get(setting, None)

async def set(self, context: str, setting: str, value: str):
self._settings[setting] = value

async def rem(self, context: str, setting: str):
if setting in self._settings:
del self._settings[setting]


class Server(BaseServer):
def __init__(self, bot: Bot, name: str, channel: str, database: Database):
super().__init__(bot, name)
Expand All @@ -78,24 +86,24 @@ async def line_read(self, line: Line):
await self.send(build("JOIN", [self._channel]))

if (
line.command == "PRIVMSG" and
self.has_channel(line.params[0]) and
not line.hostmask is None and
not self.casefold(line.hostmask.nickname) == me and
self.has_user(line.hostmask.nickname) and
line.params[1].startswith(TRIGGER)):
line.command == "PRIVMSG"
and self.has_channel(line.params[0])
and not line.hostmask is None
and not self.casefold(line.hostmask.nickname) == me
and self.has_user(line.hostmask.nickname)
and line.params[1].startswith(TRIGGER)
):

channel = self.channels[self.casefold(line.params[0])]
user = self.users[self.casefold(line.hostmask.nickname)]
cuser = channel.users[user.nickname_lower]
text = line.params[1].replace(TRIGGER, "", 1)
user = self.users[self.casefold(line.hostmask.nickname)]
cuser = channel.users[user.nickname_lower]
text = line.params[1].replace(TRIGGER, "", 1)
db_context = f"{self.name}:{channel.name}"

name, _, text = text.partition(" ")
name, _, text = text.partition(" ")
action, _, text = text.partition(" ")
name = name.lower()
key = f"factoid-{name}"

key = f"factoid-{name}"

out = ""
if not action or action == "@":
Expand Down Expand Up @@ -125,40 +133,37 @@ async def line_read(self, line: Line):
elif value:
changed = _sed(value, current)
if not changed is None:
await self._database.set(
db_context, key, changed)
out = (f"{user.nickname}: "
f"changed '{name}' factoid")
await self._database.set(db_context, key, changed)
out = f"{user.nickname}: " f"changed '{name}' factoid"
else:
out = f"{user.nickname}: invalid sed"
else:
out = f"{user.nickname}: please provide a sed"
else:
out = f"{user.nickname}: you are not an op"


else:
out = f"{user.nickname}: unknown action '{action}'"
await self.send(build("PRIVMSG", [line.params[0], out]))


class Bot(BaseBot):
def __init__(self, channel: str):
super().__init__()
self._channel = channel

def create_server(self, name: str):
return Server(self, name, self._channel, Database())


async def main(hostname: str, channel: str, nickname: str):
bot = Bot(channel)

params = ConnectionParams(
nickname,
hostname,
6697
)
params = ConnectionParams(nickname, hostname, 6697)
await bot.add_server("freenode", params)
await bot.run()


if __name__ == "__main__":
parser = ArgumentParser(description="A simple IRC bot for factoids")
parser.add_argument("hostname")
Expand Down
19 changes: 12 additions & 7 deletions examples/sasl.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,37 @@
import asyncio

from irctokens import build, Line
from irctokens import Line, build

from ircrobots import SASLSCRAM
from ircrobots import Bot as BaseBot
from ircrobots import ConnectionParams, SASLUserPass
from ircrobots import Server as BaseServer
from ircrobots import ConnectionParams, SASLUserPass, SASLSCRAM


class Server(BaseServer):
async def line_read(self, line: Line):
print(f"{self.name} < {line.format()}")

async def line_send(self, line: Line):
print(f"{self.name} > {line.format()}")


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


async def main():
bot = Bot()

sasl_params = SASLUserPass("myusername", "invalidpassword")
params = ConnectionParams(
"MyNickname",
host = "chat.freenode.invalid",
port = 6697,
sasl = sasl_params)
params = ConnectionParams(
"MyNickname", host="chat.freenode.invalid", port=6697, sasl=sasl_params
)

await bot.add_server("freenode", params)
await bot.run()


if __name__ == "__main__":
asyncio.run(main())
14 changes: 9 additions & 5 deletions examples/simple.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,30 @@
import asyncio

from irctokens import build, Line
from irctokens import Line, build

from ircrobots import Bot as BaseBot
from ircrobots import Server as BaseServer
from ircrobots import ConnectionParams
from ircrobots import Server as BaseServer

SERVERS = [("freenode", "chat.freenode.invalid")]

SERVERS = [
("freenode", "chat.freenode.invalid")
]

class Server(BaseServer):
async def line_read(self, line: Line):
print(f"{self.name} < {line.format()}")
if line.command == "001":
print(f"connected to {self.isupport.network}")
await self.send(build("JOIN", ["#testchannel"]))

async def line_send(self, line: Line):
print(f"{self.name} > {line.format()}")


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


async def main():
bot = Bot()
for name, host in SERVERS:
Expand All @@ -30,5 +33,6 @@ async def main():

await bot.run()


if __name__ == "__main__":
asyncio.run(main())
14 changes: 10 additions & 4 deletions ircrobots/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
from .bot import Bot
from .bot import Bot
from .ircv3 import Capability
from .params import (
SASLSCRAM,
ConnectionParams,
ResumePolicy,
SASLExternal,
SASLUserPass,
STSPolicy,
)
from .server import Server
from .params import (ConnectionParams, SASLUserPass, SASLExternal, SASLSCRAM,
STSPolicy, ResumePolicy)
from .ircv3 import Capability
27 changes: 13 additions & 14 deletions ircrobots/asyncs.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
from asyncio import Future
from typing import (Any, Awaitable, Callable, Generator, Generic, Optional,
TypeVar)
from asyncio import Future
from typing import Any, Awaitable, Callable, Generator, Generic, Optional, TypeVar

from irctokens import Line

from irctokens import Line
from .matching import IMatchResponse
from .interface import IServer
from .ircv3 import TAG_LABEL
from .ircv3 import TAG_LABEL
from .matching import IMatchResponse

TEvent = TypeVar("TEvent")


class MaybeAwait(Generic[TEvent]):
def __init__(self, func: Callable[[], Awaitable[TEvent]]):
self._func = func
Expand All @@ -16,13 +18,12 @@ def __await__(self) -> Generator[Any, None, TEvent]:
coro = self._func()
return coro.__await__()


class WaitFor(object):
def __init__(self,
response: IMatchResponse,
deadline: float):
def __init__(self, response: IMatchResponse, deadline: float):
self.response = response
self.deadline = deadline
self._label: Optional[str] = None
self._label: Optional[str] = None
self._our_fut: "Future[Line]" = Future()

def __await__(self) -> Generator[Any, None, Line]:
Expand All @@ -32,11 +33,9 @@ def with_label(self, label: str):
self._label = label

def match(self, server: IServer, line: Line):
if (self._label is not None and
line.tags is not None):
if self._label is not None and line.tags is not None:
label = TAG_LABEL.get(line.tags)
if (label is not None and
label == self._label):
if label is not None and label == self._label:
return True
return self.response.match(server, line)

Expand Down
30 changes: 18 additions & 12 deletions ircrobots/bot.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import asyncio, traceback
import anyio
import asyncio
import traceback
from typing import Dict

import anyio
from ircstates.server import ServerDisconnectedException

from .server import ConnectionParams, Server
from .transport import TCPTransport
from .interface import IBot, IServer, ITCPTransport
from .server import ConnectionParams, Server
from .transport import TCPTransport


class Bot(IBot):
def __init__(self):
Expand All @@ -17,9 +19,11 @@ def create_server(self, name: str):
return Server(self, name)

async def disconnected(self, server: IServer):
if (server.name in self.servers and
server.params is not None and
server.disconnected):
if (
server.name in self.servers
and server.params is not None
and server.disconnected
):

reconnect = server.params.reconnect

Expand All @@ -30,18 +34,20 @@ async def disconnected(self, server: IServer):
except Exception as e:
traceback.print_exc()
# let's try again, exponential backoff up to 5 mins
reconnect = min(reconnect*2, 300)
reconnect = min(reconnect * 2, 300)
else:
break

async def disconnect(self, server: IServer):
del self.servers[server.name]
await server.disconnect()

async def add_server(self,
name: str,
params: ConnectionParams,
transport: ITCPTransport = TCPTransport()) -> Server:
async def add_server(
self,
name: str,
params: ConnectionParams,
transport: ITCPTransport = TCPTransport(),
) -> Server:
server = self.create_server(name)
self.servers[name] = server
await server.connect(transport, params)
Expand Down
2 changes: 2 additions & 0 deletions ircrobots/contexts.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from dataclasses import dataclass

from .interface import IServer


@dataclass
class ServerContext(object):
server: IServer
Loading