mirror of
https://github.com/nexy7574/LCC-bot.git
synced 2024-09-19 18:16:34 +01:00
Add status RSS fetcher
This commit is contained in:
parent
fe0cb6dd93
commit
b842ef2acb
2 changed files with 153 additions and 3 deletions
152
cogs/events.py
152
cogs/events.py
|
@ -1,16 +1,26 @@
|
||||||
|
import hashlib
|
||||||
import io
|
import io
|
||||||
|
import json
|
||||||
|
import os
|
||||||
import random
|
import random
|
||||||
import re
|
import re
|
||||||
import asyncio
|
import asyncio
|
||||||
import textwrap
|
import textwrap
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import warnings
|
||||||
|
from datetime import datetime, timezone, timedelta
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional, Tuple
|
from typing import Optional, Tuple
|
||||||
import discord
|
import discord
|
||||||
import httpx
|
import httpx
|
||||||
from discord.ext import commands, pages
|
from discord.ext import commands, pages, tasks
|
||||||
from utils import Student, get_or_none, console
|
from utils import Student, get_or_none, console
|
||||||
from config import guilds
|
from config import guilds
|
||||||
|
try:
|
||||||
|
from config import dev
|
||||||
|
except ImportError:
|
||||||
|
dev = False
|
||||||
try:
|
try:
|
||||||
from config import OAUTH_REDIRECT_URI
|
from config import OAUTH_REDIRECT_URI
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -22,6 +32,11 @@ except ImportError:
|
||||||
GITHUB_USERNAME = None
|
GITHUB_USERNAME = None
|
||||||
GITHUB_PASSWORD = None
|
GITHUB_PASSWORD = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
from config import SPAM_CHANNEL
|
||||||
|
except ImportError:
|
||||||
|
SPAM_CHANNEL = None
|
||||||
|
|
||||||
|
|
||||||
LTR = "\N{black rightwards arrow}\U0000fe0f"
|
LTR = "\N{black rightwards arrow}\U0000fe0f"
|
||||||
RTL = "\N{leftwards black arrow}\U0000fe0f"
|
RTL = "\N{leftwards black arrow}\U0000fe0f"
|
||||||
|
@ -43,6 +58,10 @@ class Events(commands.Cog):
|
||||||
def __init__(self, bot):
|
def __init__(self, bot):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.http = httpx.AsyncClient()
|
self.http = httpx.AsyncClient()
|
||||||
|
self.fetch_discord_atom_feed.start()
|
||||||
|
|
||||||
|
def cog_unload(self):
|
||||||
|
self.fetch_discord_atom_feed.cancel()
|
||||||
|
|
||||||
# noinspection DuplicatedCode
|
# noinspection DuplicatedCode
|
||||||
async def analyse_text(self, text: str) -> Optional[Tuple[float, float, float, float]]:
|
async def analyse_text(self, text: str) -> Optional[Tuple[float, float, float, float]]:
|
||||||
|
@ -434,6 +453,137 @@ class Events(commands.Cog):
|
||||||
delete_after=30
|
delete_after=30
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@tasks.loop(minutes=10)
|
||||||
|
async def fetch_discord_atom_feed(self):
|
||||||
|
if not SPAM_CHANNEL:
|
||||||
|
return
|
||||||
|
if not self.bot.is_ready():
|
||||||
|
await self.bot.wait_until_ready()
|
||||||
|
|
||||||
|
channel = self.bot.get_channel(SPAM_CHANNEL)
|
||||||
|
if channel is None or not channel.can_send(discord.Embed()):
|
||||||
|
warnings.warn("Cannot send to spam channel, disabling feed fetcher")
|
||||||
|
return
|
||||||
|
headers = {
|
||||||
|
"User-Agent": f"python-httpx/{httpx.__version__} (Like Akregator/5.22.3); syndication"
|
||||||
|
}
|
||||||
|
|
||||||
|
file = Path.home() / ".cache" / "lcc-bot" / "discord.atom"
|
||||||
|
if not file.exists():
|
||||||
|
file.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
last_modified = discord.utils.utcnow()
|
||||||
|
if dev:
|
||||||
|
last_modified = last_modified.replace(day=1, month=last_modified.month - 1)
|
||||||
|
else:
|
||||||
|
# calculate the sha256 hash of the file, returning the first 32 characters
|
||||||
|
# this is used to check if the file has changed
|
||||||
|
_hash = hashlib.sha256()
|
||||||
|
with file.open("rb") as f:
|
||||||
|
for chunk in iter(lambda: f.read(4096), b""):
|
||||||
|
_hash.update(chunk)
|
||||||
|
_hash = _hash.hexdigest()[:32]
|
||||||
|
headers["If-None-Match"] = f'W/"{_hash}"'
|
||||||
|
last_modified = datetime.fromtimestamp(file.stat().st_mtime, tz=timezone.utc)
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = await self.http.get("https://discordstatus.com/history.atom", headers=headers)
|
||||||
|
except httpx.HTTPError as e:
|
||||||
|
console.log("Failed to fetch discord atom feed:", e)
|
||||||
|
return
|
||||||
|
|
||||||
|
if response.status_code == 304:
|
||||||
|
return
|
||||||
|
|
||||||
|
if response.status_code != 200:
|
||||||
|
console.log("Failed to fetch discord atom feed:", response.status_code)
|
||||||
|
return
|
||||||
|
|
||||||
|
with file.open("wb") as f:
|
||||||
|
f.write(response.content)
|
||||||
|
|
||||||
|
incidents_file = Path.home() / ".cache" / "lcc-bot" / "history.json"
|
||||||
|
if not incidents_file.exists():
|
||||||
|
incidents_file.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
incidents = {}
|
||||||
|
else:
|
||||||
|
with incidents_file.open("r") as f:
|
||||||
|
incidents = json.load(f)
|
||||||
|
|
||||||
|
soup = BeautifulSoup(response.content, "lxml-xml")
|
||||||
|
for entry in soup.find_all("entry"):
|
||||||
|
published_tag = entry.find("published")
|
||||||
|
updated_tag = entry.find("updated") or published_tag
|
||||||
|
published = datetime.fromisoformat(published_tag.text)
|
||||||
|
updated = datetime.fromisoformat(updated_tag.text)
|
||||||
|
if updated > last_modified:
|
||||||
|
title = entry.title.text
|
||||||
|
content = ""
|
||||||
|
soup2 = BeautifulSoup(entry.content.text, "html.parser")
|
||||||
|
sep = os.urandom(16).hex()
|
||||||
|
for br in soup2.find_all("br"):
|
||||||
|
br.replace_with(sep)
|
||||||
|
for _tag in soup2.find_all("p"):
|
||||||
|
text = _tag.get_text()
|
||||||
|
date, _content = text.split(sep, 1)
|
||||||
|
_content = _content.replace(sep, "\n")
|
||||||
|
date = re.sub(r"\s{2,}", " ", date)
|
||||||
|
try:
|
||||||
|
date = datetime.strptime(date, "%b %d, %H:%M PDT")
|
||||||
|
offset = -7
|
||||||
|
except ValueError:
|
||||||
|
date = datetime.strptime(date, "%b %d, %H:%M PST")
|
||||||
|
offset = -8
|
||||||
|
date = date.replace(year=updated.year, tzinfo=timezone(timedelta(hours=offset)))
|
||||||
|
content += f"[{discord.utils.format_dt(date)}]\n> "
|
||||||
|
content += "\n> ".join(_content.splitlines())
|
||||||
|
content += "\n\n"
|
||||||
|
|
||||||
|
_status = {
|
||||||
|
"Resolved": discord.Color.green(),
|
||||||
|
"Investigating": discord.Color.dark_orange(),
|
||||||
|
"Identified": discord.Color.orange(),
|
||||||
|
"Monitoring": discord.Color.blurple(),
|
||||||
|
}
|
||||||
|
|
||||||
|
colour = _status.get(content.splitlines()[1].split(" - ")[0], discord.Color.greyple())
|
||||||
|
|
||||||
|
if len(content) > 4096:
|
||||||
|
content = f"[open on discordstatus.com (too large to display)]({entry.link['href']})"
|
||||||
|
|
||||||
|
embed = discord.Embed(
|
||||||
|
title=title,
|
||||||
|
description=content,
|
||||||
|
color=colour,
|
||||||
|
url=entry.link["href"],
|
||||||
|
timestamp=updated
|
||||||
|
)
|
||||||
|
embed.set_author(
|
||||||
|
name="Discord Status",
|
||||||
|
url="https://discordstatus.com/",
|
||||||
|
icon_url="https://raw.githubusercontent.com/EEKIM10/LCC-bot/"
|
||||||
|
"fe0cb6dd932f9fc2cb0a26433aff8e4cce19279a/assets/discord.png"
|
||||||
|
)
|
||||||
|
embed.set_footer(
|
||||||
|
text="Published: {} | Updated: {}".format(
|
||||||
|
datetime.fromisoformat(entry.find("published").text).strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
|
updated.strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if entry.id.text not in incidents:
|
||||||
|
msg = await channel.send(embed=embed)
|
||||||
|
incidents[entry.id.text] = msg.id
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
msg = await channel.fetch_message(incidents[entry.id.text])
|
||||||
|
await msg.edit(embed=embed)
|
||||||
|
except discord.HTTPException:
|
||||||
|
msg = await channel.send(embed=embed)
|
||||||
|
incidents[entry.id.text] = msg.id
|
||||||
|
|
||||||
|
with incidents_file.open("w") as f:
|
||||||
|
json.dump(incidents, f, separators=(",", ":"))
|
||||||
|
|
||||||
|
|
||||||
def setup(bot):
|
def setup(bot):
|
||||||
bot.add_cog(Events(bot))
|
bot.add_cog(Events(bot))
|
||||||
|
|
|
@ -21,7 +21,7 @@ class Bot(commands.Bot):
|
||||||
web: Optional[Dict[str, Union[Server, Config, Task]]]
|
web: Optional[Dict[str, Union[Server, Config, Task]]]
|
||||||
|
|
||||||
def __init__(self, intents: discord.Intents, guilds: list[int], extensions: list[str]):
|
def __init__(self, intents: discord.Intents, guilds: list[int], extensions: list[str]):
|
||||||
from .db import JimmyBans, registry
|
from .db import registry
|
||||||
from .console import console
|
from .console import console
|
||||||
super().__init__(
|
super().__init__(
|
||||||
command_prefix=commands.when_mentioned_or("h!", "r!"),
|
command_prefix=commands.when_mentioned_or("h!", "r!"),
|
||||||
|
@ -32,8 +32,8 @@ class Bot(commands.Bot):
|
||||||
self.loop.run_until_complete(registry.create_all())
|
self.loop.run_until_complete(registry.create_all())
|
||||||
self.training_lock = Lock()
|
self.training_lock = Lock()
|
||||||
self.started_at = datetime.now(tz=timezone.utc)
|
self.started_at = datetime.now(tz=timezone.utc)
|
||||||
self.bans = JimmyBans()
|
|
||||||
self.console = console
|
self.console = console
|
||||||
|
self.incidents = {}
|
||||||
for ext in extensions:
|
for ext in extensions:
|
||||||
try:
|
try:
|
||||||
self.load_extension(ext)
|
self.load_extension(ext)
|
||||||
|
|
Loading…
Reference in a new issue