feat(dgn-chatops): Takumi v1.0.0
All checks were successful
Check meta / check_meta (pull_request) Successful in 20s
Check meta / check_dns (pull_request) Successful in 21s
build configuration / build_vault01 (pull_request) Successful in 1m12s
build configuration / build_storage01 (pull_request) Successful in 1m14s
build configuration / build_compute01 (pull_request) Successful in 1m22s
build configuration / build_web02 (pull_request) Successful in 1m4s
build configuration / build_rescue01 (pull_request) Successful in 1m8s
lint / check (pull_request) Successful in 23s
build configuration / build_web01 (pull_request) Successful in 1m38s
build configuration / build_geo02 (pull_request) Successful in 1m3s
build configuration / build_bridge01 (pull_request) Successful in 1m1s
build configuration / build_geo01 (pull_request) Successful in 1m11s
build configuration / push_to_cache_storage01 (pull_request) Successful in 1m18s
build configuration / push_to_cache_web02 (pull_request) Successful in 1m23s
build configuration / push_to_cache_compute01 (pull_request) Successful in 1m57s
build configuration / push_to_cache_geo02 (pull_request) Successful in 1m23s
build configuration / push_to_cache_rescue01 (pull_request) Successful in 1m30s
build configuration / push_to_cache_web01 (pull_request) Successful in 2m13s
build configuration / push_to_cache_bridge01 (pull_request) Successful in 1m15s
build configuration / push_to_cache_geo01 (pull_request) Successful in 3m18s
build configuration / build_storage01 (push) Successful in 1m13s
build configuration / build_web02 (push) Successful in 1m14s
build configuration / build_vault01 (push) Successful in 1m20s
build configuration / build_compute01 (push) Successful in 1m29s
build configuration / build_web01 (push) Successful in 1m43s
lint / check (push) Successful in 23s
build configuration / build_rescue01 (push) Successful in 1m7s
build configuration / build_bridge01 (push) Successful in 1m2s
build configuration / build_geo02 (push) Successful in 1m7s
build configuration / build_geo01 (push) Successful in 1m11s
build configuration / push_to_cache_storage01 (push) Successful in 1m24s
build configuration / push_to_cache_web02 (push) Successful in 1m19s
build configuration / push_to_cache_bridge01 (push) Successful in 1m8s
build configuration / push_to_cache_rescue01 (push) Successful in 1m26s
build configuration / push_to_cache_compute01 (push) Successful in 1m47s
build configuration / push_to_cache_geo02 (push) Successful in 1m14s
build configuration / push_to_cache_geo01 (push) Successful in 1m19s
build configuration / push_to_cache_web01 (push) Successful in 2m16s

Can schedule meet.dgnum.eu in the chat upon demand.

Signed-off-by: Ryan Lahfa <ryan@dgnum.eu>
This commit is contained in:
Ryan Lahfa 2024-09-20 23:38:58 +02:00
parent 4a275fd07e
commit 1c6124f376
7 changed files with 248 additions and 5 deletions

View file

@ -0,0 +1 @@
use nix

View file

@ -43,7 +43,12 @@ let
python3 = pkgs.python311;
python3Pkgs = python3.pkgs;
ircrobots = python3Pkgs.callPackage ./ircrobots.nix { };
ps = python3Pkgs.makePythonPath [ ircrobots ];
tortoise-orm = python3Pkgs.callPackage ./tortoise-orm.nix { };
ps = python3Pkgs.makePythonPath [
ircrobots
tortoise-orm
python3Pkgs.aiohttp
];
in
{
options.dgn-chatops = {

View file

@ -0,0 +1,31 @@
{
lib,
buildPythonPackage,
fetchFromGitHub,
poetry-core,
}:
buildPythonPackage rec {
pname = "pypika-tortoise";
version = "0.1.6";
pyproject = true;
src = fetchFromGitHub {
owner = "tortoise";
repo = "pypika-tortoise";
rev = "v${version}";
hash = "sha256-xx5FUMHh6413fsvwrEA+Q0tBmJWy00h5O6YijvrJyCE=";
};
build-system = [ poetry-core ];
pythonImportsCheck = [ "pypika" ];
meta = {
description = "";
homepage = "https://github.com/tortoise/pypika-tortoise";
changelog = "https://github.com/tortoise/pypika-tortoise/blob/${src.rev}/CHANGELOG.md";
license = lib.licenses.asl20;
maintainers = with lib.maintainers; [ raitobezarius ];
};
}

View file

@ -0,0 +1,20 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "takumi"
version = "1.0.0"
authors = [
{ name = "Ryan Lahfa", email = "ryan@dgnum.eu" },
]
description = "Fully automatic day-to-day operations at DGNum"
requires-python = ">=3.11"
classifiers = [
"Programming Language :: Python :: 3",
"Operating System :: OS Independent",
]
[project.urls]
Homepage = "https://git.dgnum.eu/DGNum/infrastructure"
Issues = "https://git.dgnum.eu/DGNum/infrastructure/issues"

View file

@ -0,0 +1,29 @@
{
pkgs ? import <nixpkgs> { },
python3 ? pkgs.python3,
}:
let
takumi = python3.pkgs.buildPythonPackage rec {
pname = "takumi";
version = "1.0.0";
pyproject = true;
src = ./.;
build-system = [ python3.pkgs.hatchling ];
dependencies = [
(python3.pkgs.callPackage ./ircrobots.nix { })
(python3.pkgs.callPackage ./tortoise-orm.nix { })
python3.pkgs.aiohttp
];
postInstall = ''
mkdir -p $out/bin
cp -v takumi.py $out/bin/takumi.py
chmod +x $out/bin/takumi.py
wrapProgram $out/bin/takumi.py --prefix PYTHONPATH : "$PYTHONPATH"
'';
};
in
pkgs.mkShell { packages = [ takumi ]; }

View file

@ -1,20 +1,106 @@
#!/usr/bin/env python3
import asyncio
from irctokens import build, Line
from ircrobots import Bot as BaseBot
from ircrobots import Server as BaseServer
from ircrobots import ConnectionParams
from irctokens.line import build, Line
from ircrobots.bot import Bot as BaseBot
from ircrobots.server import Server as BaseServer
from ircrobots.params import ConnectionParams
import aiohttp
BRIDGE_NICKNAME = "hermes"
SERVERS = [
("dgnum", "irc.dgnum.eu")
]
TEAMS = {
"fai": ("tomate", "elias", "JeMaGius", "Luj", "catvayor", "Raito"),
"marketing": ("cst1", "elias"),
"bureau": ("Raito", "JeMaGius", "Luj", "gdd")
}
# times format is 0700-29092024
TRIGGER = '!'
async def create_meet(title: str, times: list[str], timezone: str = "UTC") -> str:
async with aiohttp.ClientSession() as session:
payload = {
'name': title,
'times': times,
'timezone': timezone
}
async with session.post('https://api.meet.dgnum.eu/event', json=payload) as response:
response.raise_for_status()
id = (await response.json()).get('id')
if not id:
raise RuntimeError('No ID attributed to a meet')
return f'https://meet.dgnum.eu/{id}'
def expand_times(times: list[str]) -> list[str]:
expanded = []
# TODO: verify the date exist in the calendar
# TODO: verify that we don't write any duplicates.
for time in times:
if '-' not in time:
for i in range(7, 20):
expanded.append(f'{i:02}00-{time}')
else:
expanded.append(time)
return expanded
def bridge_stripped(possible_command: str, origin_nick: str) -> str | None:
if origin_nick.lower() == BRIDGE_NICKNAME:
stripped_user = possible_command.split(':')[1].lstrip()
return stripped_user if stripped_user.startswith(TRIGGER) else None
else:
return possible_command if possible_command.startswith(TRIGGER) else None
class Server(BaseServer):
def extract_valid_command(self, line: Line) -> str | None:
me = self.nickname_lower
if line.command == "PRIVMSG" and \
self.has_channel(line.params[0]) and \
line.hostmask is not None and \
self.casefold(line.hostmask.nickname) != me and \
self.has_user(line.hostmask.nickname):
return bridge_stripped(line.params[1], line.hostmask.nickname)
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", ["#dgnum-bridge-test"]))
# In case `!probe_meet <title> <team> <time_1> <time_2> … <time_N> [<timezone>]`
if (command := self.extract_valid_command(line)) is not None:
text = command.lstrip(TRIGGER)
if text.startswith('probe_meet'):
args = text.split(' ')
if len(args) < 4:
await self.send(build("PRIVMSG", [line.params[0], "usage is !probe_meet <title> <team> <time_1> [<time_2> <time_3> … <time_N>] ; time is in [00-hour-]DDMMYYYY format."]))
return
title, team = args[1], args[2]
print(f"creating meet '{title}' for team '{team}'")
try:
times = expand_times(args[3:])
link = await create_meet(title, times)
if team not in TEAMS:
await self.send(build("PRIVMSG", [line.params[0], f"team {team} does not exist"]))
return
targets = TEAMS[team]
ping_mentions = ', '.join(targets)
await self.send(build("PRIVMSG", [line.params[0], f'{ping_mentions} {link}']))
except ValueError as e:
print(e)
await self.send(build("PRIVMSG", [line.params[0], "time format is [00-hour-]DDMMYYYY, hour is optional, by default it's 07:00 to 19:00 in Europe/Paris timezone"]))
except aiohttp.ClientError as e:
print(e)
await self.send(build("PRIVMSG", [line.params[0], "failed to create the meet on meet.dgnum.eu, API error, check the logs"]))
async def line_send(self, line: Line):
print(f"{self.name} > {line.format()}")

View file

@ -0,0 +1,71 @@
{
lib,
buildPythonPackage,
fetchFromGitHub,
poetry-core,
aiosqlite,
iso8601,
callPackage,
pytz,
ciso8601,
orjson,
uvloop,
aiomysql,
asyncmy,
asyncpg,
psycopg,
pydantic,
pythonRelaxDepsHook,
}:
buildPythonPackage rec {
pname = "tortoise-orm";
version = "0.21.6";
pyproject = true;
src = fetchFromGitHub {
owner = "tortoise";
repo = "tortoise-orm";
rev = version;
hash = "sha256-Gu7MSJbPjaGUN6tmHwkmx7Bdy/+V1wZjmTCQrTDDPkw=";
};
buildInputs = [ pythonRelaxDepsHook ];
pythonRelaxDeps = [
"aiosqlite"
"iso8601"
];
build-system = [ poetry-core ];
dependencies = [
aiosqlite
iso8601
pydantic
(callPackage ./pypika-tortoise.nix { })
pytz
];
optional-dependencies = {
accel = [
ciso8601
orjson
uvloop
];
aiomysql = [ aiomysql ];
asyncmy = [ asyncmy ];
asyncpg = [ asyncpg ];
psycopg = [ psycopg ];
};
pythonImportsCheck = [ "tortoise" ];
meta = {
description = "";
homepage = "https://github.com/tortoise/tortoise-orm";
changelog = "https://github.com/tortoise/tortoise-orm/blob/${src.rev}/CHANGELOG.rst";
license = lib.licenses.asl20;
maintainers = with lib.maintainers; [ raitobezarius ];
};
}