From 6f125fdb1fd23b5d634cf50235f16f8c5f03e5be Mon Sep 17 00:00:00 2001 From: Felix Albrigtsen Date: Sun, 27 Aug 2023 01:51:57 +0200 Subject: [PATCH] nixify and fixify --- README.md | 6 ++ flake.lock | 26 +++++++ flake.nix | 57 ++++++++++++++ module.nix | 75 +++++++++++++++++++ pyproject.toml | 28 +++++++ src/pvv_calendar_bot/__init__.py | 1 + event.py => src/pvv_calendar_bot/event.py | 0 sendMatrix.py => src/pvv_calendar_bot/main.py | 24 +++--- .../pvv_calendar_bot/scraping.py | 4 +- 9 files changed, 209 insertions(+), 12 deletions(-) create mode 100644 README.md create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 module.nix create mode 100644 pyproject.toml create mode 100644 src/pvv_calendar_bot/__init__.py rename event.py => src/pvv_calendar_bot/event.py (100%) rename sendMatrix.py => src/pvv_calendar_bot/main.py (76%) rename scraping.py => src/pvv_calendar_bot/scraping.py (98%) diff --git a/README.md b/README.md new file mode 100644 index 0000000..5abf192 --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +# PVV Calendar -> Matrix bot + +This bot scrapes https://pvv.ntnu.no/hendelser/, fetches todays events and sends announcement messages in matrix. + +Packaged as a nix flake, see ./module.nix for options. + diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..76728a7 --- /dev/null +++ b/flake.lock @@ -0,0 +1,26 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1692986144, + "narHash": "sha256-M4VFpy7Av9j+33HF5nIGm0k2+DXXW4qSSKdidIKg5jY=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "74e5bdc5478ebbe7ba5849f0d765f92757bb9dbf", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixos-23.05", + "type": "indirect" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..dce6b47 --- /dev/null +++ b/flake.nix @@ -0,0 +1,57 @@ +{ + inputs = { + nixpkgs.url = "nixpkgs/nixos-23.05"; + }; + + outputs = { self, nixpkgs }: let + system = "x86_64-linux"; + pkgs = nixpkgs.legacyPackages.${system}; + in { + packages.${system} = { + default = self.packages.${system}.pvv-calendar-bot; + pvv-calendar-bot = pkgs.python3Packages.buildPythonPackage { + name = "pvv-calendar-bot"; + src = ./.; + format = "pyproject"; + + nativeBuildInputs = [ + pkgs.python3Packages.setuptools + ]; + + propagatedBuildInputs = with pkgs.python3Packages; [ + beautifulsoup4 + markdown2 + matrix-nio + requests + ]; + }; + }; + + nixosModules.default = ./module.nix; + + overlays.${system}.default = prevPackages: finalPackages: { + inherit (self.packages.${system}) pvv-calendar-bot; + }; + + nixosConfigurations."test" = nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + modules = [ + self.nixosModules.default + { nixpkgs.overlays = [ self.overlays."x86_64-linux".default ]; } + { + boot.isContainer = true; + + services.pvv-calendar-bot = { + enable = true; + settings.matrix = { + channel = "testchannel"; + user = "testuser"; + homeserver = "pvv.ntnu.no"; + }; + settings.secretsFile = pkgs.writeText "calendarSecrets" "snakeoil"; + }; + } + ]; + }; + }; +} diff --git a/module.nix b/module.nix new file mode 100644 index 0000000..22989b8 --- /dev/null +++ b/module.nix @@ -0,0 +1,75 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.services.pvv-calendar-bot; + inherit (lib) mkDefault mkEnableOption mkPackageOption mkIf mkOption types mdDoc; +in { + + options.services.pvv-calendar-bot = { + enable = mkEnableOption (lib.mdDoc "Enable pvv-calendar-bot to post to matrix"); + + package = mkPackageOption pkgs "pvv-calendar-bot" {}; + settings = { + onCalendar = mkOption { + type = types.str; + default = "9 0 * * *"; + description = mdDoc "OnCalendar string for the systemd service(e.g. crontab format)"; + }; + + matrix = { + user = mkOption { + type = types.str; + description = mdDoc "Matrix username to authenticate with"; + example = "@bot_calendar:pvv.ntnu.no"; + }; + channel = mkOption { + type = types.str; + description = mdDoc "Room ID of the channel to post announcements in"; + example = "!abcdef:matrix.org"; + }; + homeserver = mkOption { + type = types.str; + description = mdDoc "Matrix homeserver URL to connect to"; + example = "https://matrix.org"; + }; + }; + + secretsFile = mkOption { + type = types.path; + description = mdDoc "Path to secrets file that defines MATRIX_ACCESS_TOKEN"; + }; + + }; + }; + + config = mkIf cfg.enable { + systemd.timers."pvv-calendar-bot" = { + wantedBy = [ "timers.target" ]; + timerConfig = { + OnCalendar = cfg.settings.onCalendar; + Unit = "pvv-calendar-bot"; + }; + }; + + systemd.services."pvv-calendar-bot" = { + preStart = let + envFile = pkgs.writeText "pvv-calendar-bot-env" '' + MATRIX_URL=${cfg.settings.matrix.homeserver} + MATRIX_USER=${cfg.settings.matrix.user} + ANNOUNCEMENT_CHANNEL=${cfg.settings.matrix.channel} + MATRIX_TOKEN=@MATRIX_ACCESS_TOKEN@ + ''; + in '' + ${pkgs.replace-secret}/bin/replace-secret '@MATRIX_ACCESS_TOKEN@' ${cfg.settings.secretsFile} /run/pvv-calendar-bot/env + ''; + + serviceConfig = { + ExecStart = "${cfg.package}/bin/pvv-calendar-bot"; + RuntimeDirectory = "pvv-calendar-bot"; + DynamicUser = true; + EnvironmentFile = [ "-/run/pvv-calendar-bot/env" ]; + }; + }; + + }; + +} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..63b0dea --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,28 @@ +[build-system] +requires = ["setuptools", "setuptools-scm"] +build-backend = "setuptools.build_meta" + +[project] +name = "pvv-calendar-bot" +authors = [ + {name = "Felix Albrigtsen", email = "felix@albrigtsen.it"}, + {name = "Fredrik Robertsen", email = "fredrikrobertsen7@gmail.com"} +] +version = "1.0.0" +description = "PVV Calendar -> Matrix bot" +requires-python = ">=3.10" +keywords = [] +license = {text = "MIT"} +dependencies = [ + "beautifulsoup4", + "markdown2", + "matrix-nio", + "requests" +] + +[tool.setuptools.packages.find] +where = ["src"] +include = ["*"] + +[project.scripts] +pvv-calendar-bot = "pvv_calendar_bot:main" diff --git a/src/pvv_calendar_bot/__init__.py b/src/pvv_calendar_bot/__init__.py new file mode 100644 index 0000000..64a714f --- /dev/null +++ b/src/pvv_calendar_bot/__init__.py @@ -0,0 +1 @@ +from pvv_calendar_bot.main import main diff --git a/event.py b/src/pvv_calendar_bot/event.py similarity index 100% rename from event.py rename to src/pvv_calendar_bot/event.py diff --git a/sendMatrix.py b/src/pvv_calendar_bot/main.py similarity index 76% rename from sendMatrix.py rename to src/pvv_calendar_bot/main.py index 0d2082f..28289c9 100644 --- a/sendMatrix.py +++ b/src/pvv_calendar_bot/main.py @@ -1,4 +1,7 @@ -from event import Event +#! /usr/bin/env python + +from .event import Event +from .scraping import get_soup, process_soup, get_events_today import os from nio import AsyncClient @@ -7,8 +10,6 @@ from markdown2 import Markdown import asyncio import datetime -from scraping import get_soup, process_soup, get_events_today - MATRIX_URL=os.environ["MATRIX_URL"] MATRIX_USER=os.environ["MATRIX_USER"] MATRIX_TOKEN=os.environ["MATRIX_TOKEN"] @@ -44,18 +45,21 @@ async def sendMatrixAnnouncement(event: Event, channel: str = ANNOUNCEMENT_CHANN } ) - -async def main() -> None: +async def sendCalendarEvents() -> None: global client client = AsyncClient(MATRIX_URL, MATRIX_USER) client.access_token = MATRIX_TOKEN - s = get_soup() - es = get_events_today(process_soup(s)) + scrapeData = get_soup() + eventsToday = get_events_today(process_soup(scrapeData)) - for e in es: - await sendMatrixAnnouncement(e, ANNOUNCEMENT_CHANNEL, False) + for event in eventsToday: + await sendMatrixAnnouncement(event, ANNOUNCEMENT_CHANNEL, False) await client.close() -asyncio.run(main()) +def main(): + asyncio.run(sendCalendarEvents()) + +if __name__ == "__main__": + main() diff --git a/scraping.py b/src/pvv_calendar_bot/scraping.py similarity index 98% rename from scraping.py rename to src/pvv_calendar_bot/scraping.py index 1e2ecba..771c722 100644 --- a/scraping.py +++ b/src/pvv_calendar_bot/scraping.py @@ -1,11 +1,11 @@ +from .event import Event + from typing import List from bs4 import BeautifulSoup import requests from operator import add from functools import reduce import datetime -from event import Event - def get_soup() -> BeautifulSoup: r = requests.get("http://www.pvv.ntnu.no/hendelser/")