add /status command
it took me a while lol
This commit is contained in:
parent
e5d5cf98b2
commit
301230d7a6
9 changed files with 276 additions and 12 deletions
39
poetry.lock
generated
39
poetry.lock
generated
|
@ -151,6 +151,24 @@ docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-
|
||||||
tests = ["attrs[tests-no-zope]", "zope-interface"]
|
tests = ["attrs[tests-no-zope]", "zope-interface"]
|
||||||
tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
|
tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "beautifulsoup4"
|
||||||
|
version = "4.12.2"
|
||||||
|
description = "Screen-scraping library"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6.0"
|
||||||
|
files = [
|
||||||
|
{file = "beautifulsoup4-4.12.2-py3-none-any.whl", hash = "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a"},
|
||||||
|
{file = "beautifulsoup4-4.12.2.tar.gz", hash = "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
soupsieve = ">1.2"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
html5lib = ["html5lib"]
|
||||||
|
lxml = ["lxml"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "black"
|
name = "black"
|
||||||
version = "23.3.0"
|
version = "23.3.0"
|
||||||
|
@ -596,13 +614,13 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pluggy"
|
name = "pluggy"
|
||||||
version = "1.0.0"
|
version = "1.1.0"
|
||||||
description = "plugin and hook calling mechanisms for python"
|
description = "plugin and hook calling mechanisms for python"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.6"
|
python-versions = ">=3.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
|
{file = "pluggy-1.1.0-py3-none-any.whl", hash = "sha256:d81d19a3a88d82ed06998353ce5d5c02587ef07ee2d808ae63904ab0ccef0087"},
|
||||||
{file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
|
{file = "pluggy-1.1.0.tar.gz", hash = "sha256:c500b592c5512df35622e4faf2135aa0b7e989c7d31344194b4afb9d5e47b1bf"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
|
@ -677,6 +695,17 @@ docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-g
|
||||||
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
|
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
|
||||||
testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
|
testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "soupsieve"
|
||||||
|
version = "2.4.1"
|
||||||
|
description = "A modern CSS selector implementation for Beautiful Soup."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "soupsieve-2.4.1-py3-none-any.whl", hash = "sha256:1c1bfee6819544a3447586c889157365a27e10d88cde3ad3da0cf0ddf646feb8"},
|
||||||
|
{file = "soupsieve-2.4.1.tar.gz", hash = "sha256:89d12b2d5dfcd2c9e8c22326da9d9aa9cb3dfab0a83a024f05704076ee8d35ea"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yarl"
|
name = "yarl"
|
||||||
version = "1.9.2"
|
version = "1.9.2"
|
||||||
|
@ -767,4 +796,4 @@ multidict = ">=4.0"
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.11"
|
python-versions = "^3.11"
|
||||||
content-hash = "34afc83c805a39863cc1a214127e30c071d94387ab1efcb398df3c3d67699e46"
|
content-hash = "0cce046078dad0fc1b3edabda140722614f0e88531f5255581d5de21227f7b6d"
|
||||||
|
|
|
@ -11,6 +11,7 @@ python = "^3.11"
|
||||||
"discord.py" = "^2.3"
|
"discord.py" = "^2.3"
|
||||||
aiohttp = "^3.8"
|
aiohttp = "^3.8"
|
||||||
python-dotenv = "^1.0"
|
python-dotenv = "^1.0"
|
||||||
|
beautifulsoup4 = "^4.12"
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.dev-dependencies]
|
||||||
black = "^23.3"
|
black = "^23.3"
|
||||||
|
|
|
@ -15,6 +15,7 @@ class Wulkabot(commands.Bot):
|
||||||
help_command=None,
|
help_command=None,
|
||||||
intents=discord.Intents(guilds=True, messages=True, message_content=True),
|
intents=discord.Intents(guilds=True, messages=True, message_content=True),
|
||||||
allowed_mentions=discord.AllowedMentions.none(),
|
allowed_mentions=discord.AllowedMentions.none(),
|
||||||
|
max_ratelimit_timeout=10,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def setup_hook(self) -> None:
|
async def setup_hook(self) -> None:
|
||||||
|
|
|
@ -6,7 +6,7 @@ import discord
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
|
|
||||||
from .. import bot
|
from .. import bot
|
||||||
from ..utils import github
|
from ..utils import data_utils, github
|
||||||
from ..utils.constants import GITHUB_REPO
|
from ..utils.constants import GITHUB_REPO
|
||||||
from ..utils.views import DeleteButton
|
from ..utils.views import DeleteButton
|
||||||
|
|
||||||
|
@ -149,8 +149,7 @@ class GitHub(commands.Cog):
|
||||||
if message.author.bot:
|
if message.author.bot:
|
||||||
return
|
return
|
||||||
|
|
||||||
# `dict.fromkeys` allows us to deduplicate the list whilst preserving order
|
words = data_utils.deduplicate_list(message.content.casefold().split())
|
||||||
words = dict.fromkeys(message.content.casefold().split()).keys()
|
|
||||||
embeds = []
|
embeds = []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
66
wulkabot/cogs/vulcan_status.py
Normal file
66
wulkabot/cogs/vulcan_status.py
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
import discord
|
||||||
|
from discord import app_commands
|
||||||
|
from discord.ext import commands
|
||||||
|
|
||||||
|
from .. import bot
|
||||||
|
from ..utils import constants, vulcan_status
|
||||||
|
from ..utils.vulcan_status import Result, Status
|
||||||
|
|
||||||
|
|
||||||
|
def status_embed(status: list[tuple[str, Status]]) -> discord.Embed:
|
||||||
|
ok = []
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
for domain, result in status:
|
||||||
|
match result.state:
|
||||||
|
case Result.OK:
|
||||||
|
ok.append(domain)
|
||||||
|
continue
|
||||||
|
case Result.DATABASE_UPDATE:
|
||||||
|
icon = "🔄"
|
||||||
|
case Result.BREAK:
|
||||||
|
icon = "⚠️"
|
||||||
|
case Result.ERROR:
|
||||||
|
icon = "‼️"
|
||||||
|
case Result.TIMEOUT:
|
||||||
|
icon = "⌛"
|
||||||
|
case _:
|
||||||
|
icon = "❓"
|
||||||
|
|
||||||
|
errors.append(f"{icon} {domain}: {result.message or result.status_code}")
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
error_text = "\n".join(errors)
|
||||||
|
else:
|
||||||
|
error_text = "🟢 Wszystko działa!"
|
||||||
|
|
||||||
|
if ok:
|
||||||
|
ok_text = ", ".join(ok)
|
||||||
|
else:
|
||||||
|
ok_text = "🔥 Nic nie działa"
|
||||||
|
|
||||||
|
return (
|
||||||
|
discord.Embed(title="Status dziennika", colour=constants.ACCENT_COLOR)
|
||||||
|
.add_field(name="Błędy", value=error_text, inline=False)
|
||||||
|
.add_field(name="Działające usługi", value=ok_text, inline=False)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class VulcanStatus(commands.Cog):
|
||||||
|
def __init__(self, bot: bot.Wulkabot) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.bot = bot
|
||||||
|
|
||||||
|
@app_commands.command()
|
||||||
|
async def status(self, interaction: discord.Interaction):
|
||||||
|
"""Sprawdza status dziennika"""
|
||||||
|
await interaction.response.defer(thinking=True)
|
||||||
|
|
||||||
|
status = await vulcan_status.check_all(self.bot.http_client)
|
||||||
|
embed = status_embed(status)
|
||||||
|
|
||||||
|
await interaction.followup.send(embed=embed)
|
||||||
|
|
||||||
|
|
||||||
|
async def setup(bot: bot.Wulkabot):
|
||||||
|
await bot.add_cog(VulcanStatus(bot))
|
|
@ -5,9 +5,9 @@ from discord import app_commands
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
|
|
||||||
from .. import bot
|
from .. import bot
|
||||||
from ..utils import github, wulkanowy_manager
|
from ..utils import data_utils, github, wulkanowy_manager
|
||||||
from ..utils.constants import ACCENT_COLOR, BUILDS_CHANNEL_ID, GITHUB_REPO
|
from ..utils.constants import ACCENT_COLOR, BUILDS_CHANNEL_ID, GITHUB_REPO
|
||||||
from ..utils.wulkanowy_manager import WulkanowyBuild, WulkanowyManagerException
|
from ..utils.wulkanowy_manager import WulkanowyBuild
|
||||||
|
|
||||||
OTHER_DOWNLOADS = " | ".join(
|
OTHER_DOWNLOADS = " | ".join(
|
||||||
(
|
(
|
||||||
|
@ -46,7 +46,8 @@ class Wulkanowy(commands.Cog):
|
||||||
pulls = await self.github.fetch_open_pulls(*GITHUB_REPO)
|
pulls = await self.github.fetch_open_pulls(*GITHUB_REPO)
|
||||||
branches = ["develop"]
|
branches = ["develop"]
|
||||||
branches.extend((pull["head"]["ref"] for pull in pulls))
|
branches.extend((pull["head"]["ref"] for pull in pulls))
|
||||||
builds: list[WulkanowyBuild | WulkanowyManagerException] = await asyncio.gather(
|
branches = data_utils.deduplicate_list(branches)
|
||||||
|
builds = await asyncio.gather(
|
||||||
*map(self.wulkanowy_manager.fetch_branch_build, branches), return_exceptions=True
|
*map(self.wulkanowy_manager.fetch_branch_build, branches), return_exceptions=True
|
||||||
)
|
)
|
||||||
lines = "\n".join(
|
lines = "\n".join(
|
||||||
|
|
7
wulkabot/utils/data_utils.py
Normal file
7
wulkabot/utils/data_utils.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
from typing import TypeVar
|
||||||
|
|
||||||
|
_T = TypeVar("_T")
|
||||||
|
|
||||||
|
|
||||||
|
def deduplicate_list(data: list[_T]) -> list[_T]:
|
||||||
|
return list(dict.fromkeys(data).keys())
|
|
@ -1,4 +1,4 @@
|
||||||
from typing import Any, Literal
|
from typing import Any
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
|
||||||
|
|
160
wulkabot/utils/vulcan_status.py
Normal file
160
wulkabot/utils/vulcan_status.py
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
import asyncio
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
|
|
||||||
|
class Domain:
|
||||||
|
def __init__(self, name: str, host: str, expected_title: str) -> None:
|
||||||
|
self.name = name
|
||||||
|
self.host = host
|
||||||
|
self.expected_title = expected_title
|
||||||
|
|
||||||
|
|
||||||
|
DOMAINS: list[Domain] = [
|
||||||
|
Domain("vulcan.net.pl", "https://uonetplus.vulcan.net.pl/warszawa/", "Dziennik UONET+"),
|
||||||
|
Domain("vulcan.net.pl: Uczeń", "https://uonetplus-uczen.vulcan.net.pl/warszawa", "Uczeń"),
|
||||||
|
Domain(
|
||||||
|
"vulcan.net.pl: Wiadomości Plus",
|
||||||
|
"https://uonetplus-wiadomosciplus.vulcan.net.pl/warszawa",
|
||||||
|
"Wiadomości Plus",
|
||||||
|
),
|
||||||
|
Domain("vulcan.net.pl: Aplikacja mobilna", "https://lekcjaplus.vulcan.net.pl", "Eduone"),
|
||||||
|
Domain("umt.tarnow.pl", "https://uonetplus.umt.tarnow.pl/tarnow", "Zaloguj"),
|
||||||
|
Domain("umt.tarnow.pl: Uczeń", "https://uonetplus-uczen.umt.tarnow.pl/tarnow", "Zaloguj"),
|
||||||
|
Domain(
|
||||||
|
"umt.tarnow.pl: Wiadomości Plus",
|
||||||
|
"https://uonetplus-wiadomosciplus.umt.tarnow.pl/tarnow",
|
||||||
|
"Zaloguj",
|
||||||
|
),
|
||||||
|
Domain(
|
||||||
|
"umt.tarnow.pl: Aplikacja mobilna",
|
||||||
|
"https://uonetplus-komunikacja.umt.tarnow.pl/tarnow",
|
||||||
|
"UONET+ dla urządzeń mobilnych",
|
||||||
|
),
|
||||||
|
Domain(
|
||||||
|
"eszkola.opolskie.pl",
|
||||||
|
"https://uonetplus.eszkola.opolskie.pl/opole",
|
||||||
|
"Logowanie do systemu Opolska e-Szkola",
|
||||||
|
),
|
||||||
|
Domain(
|
||||||
|
"eszkola.opolskie.pl: Uczeń",
|
||||||
|
"https://uonetplus-uczen.eszkola.opolskie.pl/opole",
|
||||||
|
"Logowanie do systemu Opolska e-Szkola",
|
||||||
|
),
|
||||||
|
Domain(
|
||||||
|
"eszkola.opolskie.pl: Wiadomości Plus",
|
||||||
|
"https://uonetplus-wiadomosciplus.eszkola.opolskie.pl/opole",
|
||||||
|
"Logowanie do systemu Opolska e-Szkola",
|
||||||
|
),
|
||||||
|
Domain(
|
||||||
|
"eszkola.opolskie.pl: Aplikacja mobilna",
|
||||||
|
"https://uonetplus-komunikacja.eszkola.opolskie.pl/opole",
|
||||||
|
"UONET+ dla urządzeń mobilnych",
|
||||||
|
),
|
||||||
|
Domain("Rzeszów", "https://portal.vulcan.net.pl/rzeszowprojekt", "Platforma VULCAN"),
|
||||||
|
Domain(
|
||||||
|
"resman.pl: Uczeń",
|
||||||
|
"https://uonetplus-uczen.vulcan.net.pl/rzeszowprojekt",
|
||||||
|
"Logowanie do systemu",
|
||||||
|
),
|
||||||
|
Domain(
|
||||||
|
"Rzeszów: Wiadomości Plus",
|
||||||
|
"https://uonetplus-wiadomosciplus.vulcan.net.pl/rzeszowprojekt",
|
||||||
|
"Logowanie do systemu",
|
||||||
|
),
|
||||||
|
Domain("edu.gdansk.pl", "https://uonetplus.edu.gdansk.pl/gdansk", "Logowanie do systemu"),
|
||||||
|
Domain(
|
||||||
|
"edu.gdansk.pl: Uczeń",
|
||||||
|
"https://uonetplus-uczen.edu.gdansk.pl/gdansk",
|
||||||
|
"Logowanie do systemu",
|
||||||
|
),
|
||||||
|
Domain(
|
||||||
|
"edu.gdansk.pl: Wiadomości Plus",
|
||||||
|
"https://uonetplus-wiadomosciplus.edu.gdansk.pl/gdansk",
|
||||||
|
"Logowanie do systemu",
|
||||||
|
),
|
||||||
|
Domain(
|
||||||
|
"edu.gdansk.pl: Aplikacja mobilna",
|
||||||
|
"https://uonetplus-komunikacja.edu.gdansk.pl/gdansk",
|
||||||
|
"UONET+ dla urządzeń mobilnych",
|
||||||
|
),
|
||||||
|
Domain("edu.lublin.eu", "https://uonetplus.edu.lublin.eu/lublin", "Logowanie do systemu"),
|
||||||
|
Domain(
|
||||||
|
"edu.lublin.eu: Uczeń",
|
||||||
|
"https://uonetplus-uczen.edu.lublin.eu/lublin",
|
||||||
|
"Logowanie do systemu",
|
||||||
|
),
|
||||||
|
Domain(
|
||||||
|
"edu.lublin.eu: Wiadomości Plus",
|
||||||
|
"https://uonetplus-wiadomosciplus.edu.lublin.eu/lublin",
|
||||||
|
"Logowanie do systemu",
|
||||||
|
),
|
||||||
|
Domain(
|
||||||
|
"edu.lublin.eu: Aplikacja mobilna",
|
||||||
|
"https://uonetplus-komunikacja.edu.lublin.eu/lublin",
|
||||||
|
"UONET+ dla urządzeń mobilnych",
|
||||||
|
),
|
||||||
|
Domain("eduportal.koszalin.pl", "https://uonetplus.eduportal.koszalin.pl/koszalin", "Zaloguj"),
|
||||||
|
Domain(
|
||||||
|
"eduportal.koszalin.pl: Uczeń",
|
||||||
|
"https://uonetplus-uczen.eduportal.koszalin.pl/koszalin",
|
||||||
|
"Zaloguj",
|
||||||
|
),
|
||||||
|
Domain(
|
||||||
|
"eduportal.koszalin.pl: Wiadomości Plus",
|
||||||
|
"https://uonetplus-wiadomosciplus.eduportal.koszalin.pl/koszalin",
|
||||||
|
"Zaloguj",
|
||||||
|
),
|
||||||
|
Domain(
|
||||||
|
"eduportal.koszalin.pl: Aplikacja mobilna",
|
||||||
|
"https://uonetplus-komunikacja.eduportal.koszalin.pl/koszalin",
|
||||||
|
"UONET+ dla urządzeń mobilnych",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class Result(Enum):
|
||||||
|
OK = 0
|
||||||
|
DATABASE_UPDATE = 1
|
||||||
|
BREAK = 2
|
||||||
|
ERROR = 3
|
||||||
|
TIMEOUT = 4
|
||||||
|
UNKNOWN = 5
|
||||||
|
|
||||||
|
|
||||||
|
class Status:
|
||||||
|
def __init__(self, state: Result, status_code: int | None, message: str | None) -> None:
|
||||||
|
self.state = state
|
||||||
|
self.status_code = status_code
|
||||||
|
self.message = message
|
||||||
|
|
||||||
|
|
||||||
|
async def check_status(http_client: aiohttp.ClientSession, url: str, expected_title: str) -> Status:
|
||||||
|
try:
|
||||||
|
response = await http_client.get(url, timeout=10)
|
||||||
|
except asyncio.TimeoutError as e:
|
||||||
|
return Status(Result.TIMEOUT, None, "Timeout")
|
||||||
|
except aiohttp.ClientError as e:
|
||||||
|
return Status(Result.ERROR, None, str(e))
|
||||||
|
|
||||||
|
soup = BeautifulSoup(await response.text(), "html.parser")
|
||||||
|
|
||||||
|
if error_div := soup.find(id="MainPage_ErrorDiv"):
|
||||||
|
return Status(Result.ERROR, response.status, error_div.get_text())
|
||||||
|
|
||||||
|
title = soup.title.string if soup.title else None
|
||||||
|
|
||||||
|
if title == expected_title:
|
||||||
|
return Status(Result.OK, response.status, None)
|
||||||
|
|
||||||
|
return Status(Result.UNKNOWN, response.status, title)
|
||||||
|
|
||||||
|
|
||||||
|
async def check_all(http_client: aiohttp.ClientSession) -> list[tuple[str, Status]]:
|
||||||
|
status = await asyncio.gather(
|
||||||
|
*(check_status(http_client, domain.host, domain.expected_title) for domain in DOMAINS)
|
||||||
|
)
|
||||||
|
|
||||||
|
return [(domain.name, result) for (domain, result) in zip(DOMAINS, status)]
|
Loading…
Reference in a new issue