diff --git a/.env.template b/.env.template index a9a427544e3f95d75fbfd3fa88b7745286896096..6578327db75823aeafbb1aa6a06772f65231a474 100644 --- a/.env.template +++ b/.env.template @@ -19,6 +19,7 @@ DISCORD_WELCOME_CHANNEL=<ID of welcome channel> DISCORD_WELCOME_MSG=<ID of welcome message> DISCORD_SEASONAL_EVENTS_CATEGORY=<ID of Seasonal Events Category> DISCORD_ADVENT_CALENDAR_CHANNEL_2021=<ID of advent calendar chanel for 2021> +DISCORD_JOBOFFERS_CHANNEL=<ID of stellenangebote Channel> # JSON Files DISCORD_CALMDOWN_FILE=<File name for calmdowns JSON file> @@ -28,7 +29,10 @@ DISCORD_TEXT_COMMANDS_FILE=<File name for text commands JSON file> DISCORD_TIMER_FILE=<File name for running timers JSON file> DISCORD_APPOINTMENTS_FILE=<File name for running appointments JSON file> DISCORD_ADVENT_CALENDAR_FILE=<File name for advent calendar JSON file> +DISCORD_JOBOFFERS_FILE=<File name for joboffers JSON file> # Misc DISCORD_DATE_TIME_FORMAT=<Date and time format used for commands like %d.%m.%Y %H:%M> DISCORD_ADVENT_CALENDAR_START=<Start date and time for advent calendar. Something like "01.12.2021 00:00"> +DISCORD_JOBOFFERS_URL=<url from which joboffers are fetched, atm "https://www.fernuni-hagen.de/uniintern/arbeitsthemen/karriere/stellen/include/hk.shtml"> +DISCORD_JOBOFFERS_STD_FAK=<faculty for which joboffers should be postet, one of [mi|rewi|wiwi|ksw|psy|other|all]> diff --git a/cogs/job_offers.py b/cogs/job_offers.py new file mode 100644 index 0000000000000000000000000000000000000000..9546ed7057e8ff27320d165b4207e54880d82182 --- /dev/null +++ b/cogs/job_offers.py @@ -0,0 +1,162 @@ +import json +import os +from copy import deepcopy + +import aiohttp +from bs4 import BeautifulSoup +import disnake +from disnake import ApplicationCommandInteraction +from disnake.ext import commands, tasks +from cogs.help import help + +""" + Environment Variablen: + DISCORD_JOBOFFERS_FILE - json file mit allen aktuellen + DISCORD_JOBOFFERS_CHANNEL - Channel-ID für Stellenangebote + DISCORD_JOBOFFERS_URL - URL von der die Stellenangebote geholt werde + DISCORD_JOBOFFERS_STD_FAK - Fakultät deren Stellenangebote standardmäßig gepostet werden + + Struktur der json: + {fak:{id:{title:..., info:..., link:..., deadline:...}} + mit fak = [mi|rewi|wiwi|ksw|psy|other] +""" + +JOBS_URL = os.getenv("DISCORD_JOBOFFERS_URL") +STD_FAK = os.getenv("DISCORD_JOBOFFERS_STD_FAK") + +class Joboffers(commands.Cog): + def __init__(self, bot): + self.bot = bot + self.joboffers = {} + self.joboffers_channel_id = int(os.getenv("DISCORD_JOBOFFERS_CHANNEL")) + self.joboffers_file = os.getenv("DISCORD_JOBOFFERS_FILE") + self.load_joboffers() + self.update_loop.start() + + @tasks.loop(hours=24) + async def update_loop(self): + await self.fetch_joboffers() + + @update_loop.before_loop + async def before_update_loop(self): + await self.bot.wait_until_ready() + + def save_joboffers(self): + joboffers_file = open(self.joboffers_file, mode='w') + json.dump(self.joboffers, joboffers_file) + + def load_joboffers(self): + try: + joboffers_file = open(self.joboffers_file, mode='r') + self.joboffers = json.load(joboffers_file) + except FileNotFoundError: + self.joboffers = {} + + @help( + syntax="/jobs <fak?>", + parameters={ + "fak": "Fakultät für die die studentische Hilfskraft Jobs ausgegeben werden sollen " + "(mi, rewi, wiwi, ksw, psy)" + }, + brief="Ruft Jobangebote für Studiernde der Fernuni Hagen auf." + ) + @commands.slash_command(name="jobs", aliases=["offers","stellen","joboffers"], + description="Liste Jobangebote der Uni auf", + options=[disnake.Option(name="faculty", + description="Fakultät", + choices=[disnake.OptionChoice('mi','mi'), + disnake.OptionChoice('rewi','rewi'), + disnake.OptionChoice('wiwi','wiwi'), + disnake.OptionChoice('ksw','ksw'), + disnake.OptionChoice('psy','psy'), + disnake.OptionChoice('other','other'), + disnake.OptionChoice('all','all')])]) + async def cmd_jobs(self, interaction: ApplicationCommandInteraction, faculty: str = STD_FAK): + await self.fetch_joboffers() + + fak_text = "aller Fakultäten" if faculty == 'all' else f"der Fakultät {faculty}" + + embed = disnake.Embed(title="Stellenangebote der Uni", + description=f"Ich habe folgende Stellenangebote {fak_text} gefunden:") + + for fak, fak_offers in self.joboffers.items(): + if STD_FAK != 'all' and fak != faculty: + continue + for offer_id, offer_data in fak_offers.items(): + descr = f"{offer_data['info']}\nDeadline: {offer_data['deadline']}\n{offer_data['link']}" + embed.add_field(name=offer_data['title'], value=descr, inline=False) + + await interaction.response.send_message(embed=embed, ephemeral=True) + + + async def post_new_jobs(self, jobs): + fak_text = "aller Fakultäten" if STD_FAK == 'all' else f"der Fakultät {STD_FAK}" + embed = disnake.Embed(title="Neue Stellenangebote der Uni", + description=f"Ich habe folgende neue Stellenangebote {fak_text} gefunden:") + for job in jobs: + descr = f"{job['info']}\nDeadline: {job['deadline']}\n{job['link']}" + embed.add_field(name=job['title'], value=descr, inline=False) + + joboffers_channel = await self.bot.fetch_channel(self.joboffers_channel_id) + await joboffers_channel.send(embed=embed) + + + async def fetch_joboffers(self): + sess = aiohttp.ClientSession() + req = await sess.get(JOBS_URL) + text = await req.read() + await sess.close() + + soup = BeautifulSoup(text, "html.parser") + list = soup.findAll("li") + + # alte Liste sichern zum Abgleich + old_joboffers = deepcopy(self.joboffers) + + for job in list: + detail_string = job.text.strip() + if "Studentische Hilfskraft" in detail_string: + id = detail_string[detail_string.index('(')+12:detail_string.index(')')] + title = detail_string[:detail_string.index(')')+1] + info = detail_string[detail_string.index(')')+1:detail_string.index('[')] + deadline = detail_string[detail_string.index('[')+1:detail_string.index(']')] + link = job.find('a')['href'] + + # Umlaute aufräumen + info = info.replace("ä","ä") + info = info.replace("ü", "ü") + + faks = ["other", "wiwi", "mi", "ksw", "psy", "rewi"] + + fak_id = int(id[0]) # Kennziffer 1=wiwi, 2=mi, 3=ksw, 4=psy, 5=rewi, alle anderen=other + if fak_id in range(1,6): + fak = faks[fak_id] + else: + fak = faks[0] + + if not self.joboffers.get(fak): + self.joboffers[fak] = {} + self.joboffers[fak][id] = {'title': title, 'info': info, 'deadline': deadline, 'link': link} + self.save_joboffers() + await self.check_for_new_jobs(old_joboffers) + + async def check_for_new_jobs(self, old_joboffers): + new_jobs = [] + + for fak, fak_offers in self.joboffers.items(): + if STD_FAK != 'all' and fak != STD_FAK: + continue + + if fak_old := old_joboffers.get(fak): + for offer_id, offer_data in fak_offers.items(): + if old_offer := fak_old.get(offer_id): + if offer_data != old_offer: + new_jobs.append(offer_data) + else: + new_jobs.append(offer_data) + else: + for offer_id, offer_data in self.joboffers.get(fak).items(): + new_jobs.append(offer_data) + + if new_jobs: + await self.post_new_jobs(new_jobs) diff --git a/root.py b/root.py index 384ff54544844fe78888f02a8a5c4147b73bb04a..4ce6d87642275719dd6b0f5499e354e13aab6916 100644 --- a/root.py +++ b/root.py @@ -4,7 +4,8 @@ import disnake from disnake.ext import commands from dotenv import load_dotenv -from cogs import appointments, calmdown, help, links, polls, roles, support, text_commands, timer, welcome, christmas +from cogs import appointments, calmdown, help, links, polls, roles, support, text_commands, timer, welcome, christmas, \ + job_offers # .env file is necessary in the same directory, that contains several strings. load_dotenv() @@ -39,6 +40,7 @@ class Root(commands.Bot): self.add_cog(timer.Timer(self)) self.add_cog(welcome.Welcome(self)) self.add_cog(christmas.Christmas(self)) + self.add_cog(job_offers.Joboffers(self)) bot = Root()