import os import discord import yaml import requests from datetime import date import urllib.parse from random import shuffle # To send discord messages (fucking async functions…) import asyncio from apscheduler.schedulers.background import BackgroundScheduler scheduler = BackgroundScheduler() scheduler.start() from dotenv import load_dotenv load_dotenv() from email.message import EmailMessage import smtplib, ssl # Create a secure SSL context ssl_context = ssl.create_default_context() def load_guilds_data(): with open("guilds.yml", 'r') as stream: try: return yaml.safe_load(stream) except yaml.YAMLError as exc: print(exc) def send_mass_mail(guild, subject, content): send_mail(guild, guild['mailing'], subject, content) def send_mail(guild, to, subject, content): msg = EmailMessage() msg['Subject'] = subject msg['From'] = guild['mail_from'] msg['To'] = to msg.set_content(content) with smtplib.SMTP_SSL(guild['smtp_host'], guild['smtp_port'], context=ssl_context) as server: server.login(guild['smtp_username'], guild['smtp_pass']) server.send_message(msg) def mail_message(message): # TODO add … if message is not complete on the subject send_mass_mail(guilds[message.guild.id], f'[Mutubot] Message discord de {message.author.display_name} : {message.content:.50}', f'{message.author.display_name}:\n{message.content}') def req(url, data): x = requests.post(url, headers={'Content-Type': 'application/x-www-form-urlencoded'}, data=data) if x.status_code != 200: print('ERROR', x) print(x.content) raise Exception('Request error ' + str(x.status_code)) # Load some data TOKEN = os.getenv('DISCORD_TOKEN') guilds = load_guilds_data() reminder_channels = [] for i in guilds: if 'reminder_channel' in guilds[i]: reminder_channels.append(guilds[i]['reminder_channel']) from html.parser import HTMLParser class TokenFinder(HTMLParser): def __init__(self, admin_url): self.token = None self.public_link = None self.delete_links = [] self.public_links = [] self.admin_url = admin_url super().__init__() def handle_starttag(self, tag, attrs_tuple): if tag != 'input' and tag != 'a': return attrs = dict(attrs_tuple) if tag == 'input' and 'type' in attrs and attrs['type'] == 'hidden' and 'name' in attrs and attrs['name'] == 'control' and 'value' in attrs : self.token = attrs['value'] elif tag == 'input' and 'id' in attrs and attrs['id'] == 'public-link' and 'value' in attrs : self.public_link = attrs['value'] elif tag == 'a' and 'href' in attrs and attrs['href'].startswith(self.admin_url + '/action/delete_column/') : self.delete_links.append(attrs['href']) elif tag == 'a' and 'href' in attrs and self.public_link and attrs['href'].startswith(self.public_link + '/vote/') : self.public_links.append(attrs['href']) def scrap_framavote (admin_url): # Parse html to find data finder = TokenFinder(admin_url) finder.feed(requests.get(admin_url).content.decode('UTF-8')) return finder def create_framavote (guild, names): finder = scrap_framavote(guild['framavote']) # Remove everything erase_framadate(guild['framavote'], finder.delete_links) # Add columns for name in names: req(guild['framavote'], 'choice='+name+'&confirm_add_column=') # Update control sum finder = scrap_framavote(guild['framavote']) # Random order shuffle(guild['members']) # Add lines for i in range(len(guild['members'])): create_line_framadate(finder.token, guild['framavote'], 'Anne ONyme'+str(i), names) # Update links finder = scrap_framavote(guild['framavote']) # Send links for mail,link in zip(guild['members'], finder.public_links): content = f""" Ce mail remplace tous les précédents s’il y en a ! Vous avez été convié à un vote anonyme. Voici le lien où voter : {link} """ send_mail(guild, mail, '[Mutubot] Vous êtes convié à un vote anonyme', content) return finder.public_link def erase_framadate (admin_url, delete_links=[]): req(admin_url, 'remove_all_votes=') req(admin_url, 'confirm_remove_all_votes=') for link in delete_links: requests.get(link) def create_line_framadate (token, admin_url, voter, names): data = 'control=' + token + '&name=' + voter + ''.join('&choices%5B'+str(i)+'%5D=+' for i in range(len(names))) + '&save=' x = req(admin_url, data) @scheduler.scheduled_job('cron', day=25) def cleaner (): for guild_id in guilds: erase_framadate(guilds[guild_id]['framadate_week']) erase_framadate(guilds[guild_id]['framadate_weekend']) # TODO erase calc revenus ? @scheduler.scheduled_job('cron', day=1) def reminder (): for i in guilds: print(f"reminding {i} : {guilds[i]['mailing']}") message = generate_reminder_message(guilds[i]) send_mass_mail(guilds[i], '[Mutubot] La mutunion c’est bientôt !', message) channel = client.get_channel(guilds[i]['reminder_channel']) asyncio.run_coroutine_threadsafe(channel.send(message), client.loop) def generate_reminder_message (guild): # If the 10 is weekend sondage = guild['framadate_week'] if date.today().replace(day=10).weekday() > 4: sondage = guild['framadate_weekend'] return f""" Coucou ! Il est l’heure de déclarer ses revenus : <{guild['link_declaration']}> Et d’annoncer à quelle heure vous souhaitez faire la mutunion : <{sondage}> Bon début de mois :D Le mutubot """ # Discord API intents = discord.Intents.default() intents.message_content = True client = discord.Client(intents=intents) randomvote = None @client.event async def on_ready(): print(f'{client.user} is connected to the following guild:\n') for guild in client.guilds: print(f'{guild.name} (id: {guild.id})') @client.event async def on_message(message): global randomvote if message.author == client.user: return if message.guild.id not in guilds: return if message.content.startswith('!randomvote '): await message.reply('Créer un vote anonyme va détruire le framavote actuel. Êtes vous sûr·e ? Répondez « Pamplemousse agrivoltaiste » pour confirmer') randomvote = message.content.split(' ')[1:] return elif message.content == 'Pamplemousse agrivoltaiste' and message.type == discord.MessageType.reply : public_link = create_framavote(guilds[message.guild.id], randomvote) randomvote = None await message.reply("C’est fait ! Vérifiez bien par vous même parce que je suis un peu fini à l’arache… " + public_link) return elif message.channel.id in guilds[message.guild.id]['mailed_channels']: mail_message(message) # Actually starts the bot client.run(TOKEN)