Working
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
|
||||
import discord_api
|
||||
import tiss
|
||||
|
||||
from dotenv import load_dotenv
|
||||
import os
|
||||
|
||||
try: import tomllib
|
||||
except ModuleNotFoundError: import pip._vendor.tomli as tomllib
|
||||
|
||||
|
||||
load_dotenv()
|
||||
|
||||
if __name__ == '__main__':
|
||||
# course_nr = tiss.TISS.get_code_from_any('https://tiss.tuwien.ac.at/course/courseDetails.xhtml?dswid=7270&dsrid=953&courseNr=186140&semester=2024W')
|
||||
# lva = tiss.TISS.get_lva(course_nr)
|
||||
|
||||
# print(lva)
|
||||
# print(lva.curricula)
|
||||
|
||||
config_file = os.getenv("CONFIG_FILE", "config.toml")
|
||||
discord_token = os.getenv("DISCORD_TOKEN")
|
||||
|
||||
if discord_token is None:
|
||||
print("[ERROR] Please set the DISCORD_TOKEN environment variable")
|
||||
exit()
|
||||
|
||||
with open(config_file, "rb") as f:
|
||||
config = tomllib.load(f)
|
||||
|
||||
discord_api.run(discord_token, config)
|
||||
99
master-bot/discord_api.py
Normal file
99
master-bot/discord_api.py
Normal file
@@ -0,0 +1,99 @@
|
||||
import nextcord
|
||||
from nextcord.ext import commands, application_checks
|
||||
|
||||
import tiss
|
||||
|
||||
cache = {}
|
||||
|
||||
def run(token: str, config: dict):
|
||||
bot = commands.Bot()
|
||||
|
||||
def get_channel(id):
|
||||
global cache
|
||||
if id not in cache:
|
||||
cache[id] = bot.get_channel(id)
|
||||
|
||||
return cache[id]
|
||||
|
||||
|
||||
@bot.slash_command(description="Manager master channels")
|
||||
@application_checks.has_guild_permissions(administrator=True)
|
||||
async def master(
|
||||
interaction: nextcord.Interaction,
|
||||
):
|
||||
pass
|
||||
|
||||
@master.subcommand(description="Sync Master LVA with Channel")
|
||||
@application_checks.has_guild_permissions(administrator=True)
|
||||
async def sync(
|
||||
interaction: nextcord.Interaction,
|
||||
link: str = nextcord.SlashOption(description="Enter TISS link")
|
||||
):
|
||||
lva_code = tiss.TISS.get_code_from_any(link)
|
||||
|
||||
if lva_code == None:
|
||||
await interaction.send("Invalid TISS link", ephemeral=True)
|
||||
return
|
||||
|
||||
try:
|
||||
lva = tiss.TISS.get_lva(lva_code)
|
||||
except:
|
||||
await interaction.send("Could not fetch TISS data", ephemeral=True)
|
||||
return
|
||||
|
||||
if lva is None:
|
||||
await interaction.send("Could not fetch TISS data", ephemeral=True)
|
||||
return
|
||||
|
||||
forum = get_channel(config["channels"]["forum"])
|
||||
|
||||
# Get forum data
|
||||
# Get tags
|
||||
tags_available = forum.available_tags
|
||||
thread_tags = []
|
||||
|
||||
config_curricula = config["curricula"]
|
||||
# This should work? but doesn't somehow
|
||||
# for curriculum in lva.curricula:
|
||||
# if curriculum in config_curricula:
|
||||
# print(f"Curriculum found: {curriculum}")
|
||||
# try:
|
||||
# print(f"Looking for id {config_curricula[curriculum]}")
|
||||
# tags.append(forum.get_tag(id = config_curricula[curriculum]))
|
||||
# print("Tag found")
|
||||
# except:
|
||||
# print(f"Tag not found for curriculum {curriculum}")
|
||||
# else:
|
||||
# print(f"Curriculum not found: {curriculum}")
|
||||
|
||||
for curriculum in config_curricula:
|
||||
if curriculum in lva.curricula:
|
||||
# Find the tag in the available tags
|
||||
tag = next(x for x in tags_available if x.id == config_curricula[curriculum])
|
||||
if tag is not None:
|
||||
thread_tags.append(tag)
|
||||
|
||||
# Get name
|
||||
thread_name = f"{lva.type} {lva.name} ({lva.id_pretty()})"
|
||||
|
||||
# Get content
|
||||
thread_content = tiss.TISS.get_anonymous_tiss_link(lva.id)
|
||||
|
||||
# Determine if the thread already exists
|
||||
for thread in forum.threads:
|
||||
# Does the thread name contain the LVA code?
|
||||
if lva.id_pretty() in thread.name:
|
||||
await thread.edit(name = thread_name, applied_tags = thread_tags)
|
||||
|
||||
message = await thread.fetch_message(thread.id)
|
||||
await message.edit(content = thread_content)
|
||||
|
||||
await interaction.send(f"Existing forum thread has been synced {thread.jump_url}", ephemeral=False)
|
||||
return
|
||||
|
||||
# If it doesn't exist, create a new thread
|
||||
thread = await forum.create_thread(name = thread_name, content = thread_content, applied_tags = thread_tags)
|
||||
reply_message = f"Forum channel created! {thread.jump_url}{" No tags were found, are you sure this was a Master LVA?" if len(thread_tags) == 0 else ""}"
|
||||
await interaction.send(reply_message, ephemeral=False)
|
||||
|
||||
bot.run(token)
|
||||
98
master-bot/tiss.py
Normal file
98
master-bot/tiss.py
Normal file
@@ -0,0 +1,98 @@
|
||||
import requests
|
||||
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
import re
|
||||
|
||||
from bs4 import BeautifulSoup, NavigableString
|
||||
import random
|
||||
|
||||
|
||||
session = requests.Session()
|
||||
|
||||
session.headers.update({
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:130.0) Gecko/20100101 Firefox/130.0'
|
||||
})
|
||||
|
||||
|
||||
class LVA:
|
||||
def __init__(self, id: str, name: str, ects: float, curricula: list[str], type: str):
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.ects = ects
|
||||
self.curricula = curricula
|
||||
self.type = type
|
||||
|
||||
def id_pretty(self):
|
||||
# format 123123 -> 123.123
|
||||
return f'{self.id[:3]}.{self.id[3:]}'
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.name} ({self.id}) - {self.ects} ECTS'
|
||||
|
||||
def __repr__(self):
|
||||
return f'{self.name} ({self.id})'
|
||||
|
||||
def get_direct_text_clean(element):
|
||||
text = ''.join(child for child in element.children if isinstance(child, NavigableString))
|
||||
return re.sub(r'\s+', ' ', text).strip()
|
||||
|
||||
|
||||
def gen_request_tokens():
|
||||
request_token = str(random.randint(0, 999))
|
||||
window_id = str(random.randint(0, 999))
|
||||
|
||||
session.cookies.set(f"dsrwid-{request_token}", window_id)
|
||||
return (request_token, window_id)
|
||||
|
||||
|
||||
class TISS:
|
||||
@staticmethod
|
||||
def get_anonymous_tiss_link(code: str):
|
||||
return f"https://tiss.tuwien.ac.at/course/courseDetails.xhtml?courseNr={code}"
|
||||
|
||||
@staticmethod
|
||||
def get_lva(code: str):
|
||||
tokens = gen_request_tokens()
|
||||
|
||||
res = session.get(f"https://tiss.tuwien.ac.at/course/courseDetails.xhtml?courseNr={code}&dsrid={tokens[0]}&dswid={tokens[1]}")
|
||||
soup = BeautifulSoup(res.text, 'html.parser')
|
||||
|
||||
course_name = get_direct_text_clean(soup.select('#contentInner > h1')[0])
|
||||
|
||||
subheader = soup.select('#subHeader')[0].text
|
||||
ects = float(re.search(r'(\d+\.\d+)EC', subheader).group(1))
|
||||
# extract "VU" from "2024W, VU, 2.0h, 3.0EC"
|
||||
type = subheader.split(",")[1].strip()
|
||||
|
||||
|
||||
table = soup.select('th[aria-label="Studienkennzahl"]')[0].parent.parent.parent
|
||||
curricula = [re.sub("\D", "", tr.find("td").text) for tr in table.find("tbody").find_all("tr")]
|
||||
|
||||
lva = LVA(code, course_name, ects, curricula, type)
|
||||
|
||||
return lva
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_code_from_any(any: str):
|
||||
# Example: https://tiss.tuwien.ac.at/course/courseDetails.xhtml?dswid=5304&dsrid=381&courseNr=180771&semester=2024W
|
||||
any = any.strip()
|
||||
url = urlparse(any)
|
||||
|
||||
if all([url.scheme, url.netloc]):
|
||||
# Yes, this was an URL
|
||||
params = parse_qs(url.query)
|
||||
|
||||
course_nr = params.get('courseNr', [None])[0]
|
||||
return course_nr
|
||||
else:
|
||||
if len(any) == 6:
|
||||
return any
|
||||
|
||||
match = re.search(r'\w{3}\.\w{3}', any)
|
||||
|
||||
if match:
|
||||
return match.group(0).replace(".", "")
|
||||
else:
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user