diff --git a/po/cs.popie b/po/cs.popie index c2494c6..7bf4604 100644 --- a/po/cs.popie +++ b/po/cs.popie @@ -19,8 +19,8 @@ msgstr Komentář odebrán. msgid Unknown author msgstr Neznámý autor. -msgid Please use either a reply or provide a number of messages to delete. Not both. -msgstr Prosím použij odpověď na zprávu, nebo počet zpráv ke smazání. Ne obojí. +msgid Please use either a reply or provide a positive number of messages to delete. Not both. +msgstr Prosím použij odpověď na zprávu, nebo kladný počet zpráv ke smazání. Ne obojí. msgid Confirm delete. msgstr Potvrzení smazání. @@ -37,8 +37,8 @@ msgstr Akce zrušena. msgid Number of messages to delete msgstr Počet zpráv ke smazání -msgid Please use either a reply or provide a number of messages to delete. -msgstr Prosím použij odpověď na zprávu, nebo počet zpráv ke smazání. +msgid Please use either a reply or provide a positive number of messages to delete. +msgstr Prosím použij odpověď na zprávu, nebo kladný počet zpráv ke smazání. msgid Deleted {deleted} message(s) msgstr Smazáno {deleted} zpráv. diff --git a/po/sk.popie b/po/sk.popie index 48d75a6..8d8b2ff 100644 --- a/po/sk.popie +++ b/po/sk.popie @@ -19,8 +19,8 @@ msgstr Komentár odobraný. msgid Unknown author msgstr Neznámy autor. -msgid Please use either a reply or provide a number of messages to delete. Not both. -msgstr Prosím použi odpoveď na správu, alebo počet správ na zmazanie. Nie oboje. +msgid Please use either a reply or provide a positive number of messages to delete. Not both. +msgstr Prosím použi odpoveď na správu, alebo kladný počet správ na zmazanie. Nie oboje. msgid Confirm delete. msgstr Potvrdenie zmazania @@ -37,8 +37,8 @@ msgstr Akcia zrušená. msgid Number of messages to delete msgstr Počet správ na vymazanie. -msgid Please use either a reply or provide a number of messages to delete. -msgstr Prosím použi odpoveď na správu, alebo počet správ na zmazanie. +msgid Please use either a reply or provide a positive number of messages to delete. +msgstr Prosím použi odpoveď na správu, alebo kladný počet správ na zmazanie. msgid Deleted {deleted} message(s) msgstr Zmazané {deleted} správ. diff --git a/purge/module.py b/purge/module.py index 8529aae..cfc713d 100644 --- a/purge/module.py +++ b/purge/module.py @@ -24,7 +24,7 @@ def _not_pinned(message: discord.Message) -> bool: async def purge(self, ctx: commands.Context, count: Optional[int] = None): """Purge spam messages. - Either reply to the oldest message you want to keep or provide a number of messages to delete. + Either reply to the oldest message you want to keep or provide a positive number of messages to delete. This command keeps pinned messages intact. """ @@ -35,7 +35,7 @@ async def purge(self, ctx: commands.Context, count: Optional[int] = None): await ctx.reply( _( ctx, - "Please use either a reply or provide a number of messages to delete. Not both.", + "Please use either a reply or provide a positive number of messages to delete. Not both.", ) ) return @@ -69,7 +69,7 @@ async def purge(self, ctx: commands.Context, count: Optional[int] = None): await ctx.send(_(ctx, "Aborted.")) return - elif count is not None: + elif count is not None and count > 0: if count > 10: embed = utils.discord.create_embed( author=ctx.author, @@ -107,7 +107,7 @@ async def purge(self, ctx: commands.Context, count: Optional[int] = None): await ctx.reply( _( ctx, - "Please use either a reply or provide a number of messages to delete.", + "Please use either a reply or provide a positive number of messages to delete.", ) ) return @@ -117,8 +117,10 @@ async def purge(self, ctx: commands.Context, count: Optional[int] = None): ctx.channel, f"Deleted {len(deleted)} message(s)", ) + await ctx.message.delete() await channel.send( - _(ctx, "Deleted {deleted} message(s)").format(deleted=len(deleted)) + _(ctx, "Deleted {deleted} message(s)").format(deleted=len(deleted)), + delete_after=10, ) diff --git a/verify/module.py b/verify/module.py index c4fff3f..b35aad1 100644 --- a/verify/module.py +++ b/verify/module.py @@ -1,20 +1,25 @@ import asyncio +import base64 import contextlib import datetime +import email import json import os import random import re import smtplib +import ssl import string import tempfile +import urllib + import unidecode from typing import Dict, List, Union, Optional from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart -import imap_tools +import imaplib import discord from discord.ext import commands @@ -35,7 +40,10 @@ SMTP_SERVER: str = os.getenv("SMTP_SERVER") IMAP_SERVER: str = os.getenv("IMAP_SERVER") SMTP_ADDRESS: str = os.getenv("SMTP_ADDRESS") -SMTP_PASSWORD: str = os.getenv("SMTP_PASSWORD") + +CLIENT_ID: str = os.getenv("CLIENT_ID") +CLIENT_SECRET: str = os.getenv("CLIENT_SECRET") +CLIENT_REFRESH_TOKEN: str = os.getenv("CLIENT_REFRESH_TOKEN") def test_dotenv() -> None: @@ -43,10 +51,14 @@ def test_dotenv() -> None: raise exceptions.DotEnvException("SMTP_SERVER is not set.") if type(SMTP_ADDRESS) != str: raise exceptions.DotEnvException("SMTP_ADDRESS is not set.") - if type(SMTP_PASSWORD) != str: - raise exceptions.DotEnvException("SMTP_PASSWORD is not set.") + if type(CLIENT_ID) != str: + raise exceptions.DotEnvException("CLIENT_ID is not set.") if type(IMAP_SERVER) != str: raise exceptions.DotEnvException("IMAP_SERVER is not set.") + if type(CLIENT_SECRET) != str: + raise exceptions.DotEnvException("CLIENT_SECRET is not set.") + if type(CLIENT_REFRESH_TOKEN) != str: + raise exceptions.DotEnvException("CLIENT_REFRESH_TOKEN is not set.") test_dotenv() @@ -1036,8 +1048,10 @@ async def _send_email( """Send the verification e-mail.""" try: with smtplib.SMTP_SSL(SMTP_SERVER) as server: + _auth_string = self._refresh_token(CLIENT_ID, CLIENT_SECRET, CLIENT_REFRESH_TOKEN)["access_token"] + _auth_string = self._generate_oauth_2_string(SMTP_ADDRESS, _auth_string, base64_encode=False) server.ehlo() - server.login(SMTP_ADDRESS, SMTP_PASSWORD) + server.docmd('AUTH', 'XOAUTH2 ' + base64.b64encode(_auth_string.encode('utf-8')).decode('utf-8')) server.send_message(message) return True except smtplib.SMTPException as exc: @@ -1107,51 +1121,103 @@ def _check_inbox_for_errors(self): """ unread_messages = [] - with imap_tools.MailBox(IMAP_SERVER).login( - SMTP_ADDRESS, SMTP_PASSWORD - ) as mailbox: - messages = [ - m - for m in mailbox.fetch( - imap_tools.AND(seen=False), - mark_seen=False, - ) - ] - mark_as_read: List = [] + with imaplib.IMAP4_SSL(IMAP_SERVER, ssl_context=ssl.create_default_context()) as mailbox: + _auth_string = self._refresh_token(CLIENT_ID, CLIENT_SECRET, CLIENT_REFRESH_TOKEN)["access_token"] + _auth_string = self._generate_oauth_2_string(SMTP_ADDRESS, _auth_string, base64_encode=False) + mailbox.authenticate('XOAUTH2', lambda x: _auth_string) + mailbox.select("INBOX", readonly=False) + status, data = mailbox.search(None, 'UNSEEN') + message_ids = data[0].split() + print("haloooooooo") + for msg_id in message_ids: + status, msg_data = mailbox.fetch(msg_id, '(RFC822)') + if status != 'OK': + continue - for m in messages: - has_delivery_status: bool = False + msg = email.message_from_bytes(msg_data[0][1]) + has_delivery_status = False - for part in m.obj.walk(): - if part.get_content_type() == "message/delivery-status": + # Procházení částí zprávy + if msg.is_multipart(): + for part in msg.walk(): + if part.get_content_type() == 'message/delivery-status': + has_delivery_status = True + break + else: + # Pokud zpráva není multipart, kontrolujeme přímo + if msg.get_content_type() == 'message/delivery-status': has_delivery_status = True - break if not has_delivery_status: continue - rfc_message = m.obj.as_string() + # Převedení celé zprávy na string kvůli hledání hlaviček + rfc_message = msg.as_string() info: dict = {} - for line in rfc_message.split("\n"): + for line in rfc_message.split('\n'): if line.startswith(MAIL_HEADER_PREFIX): - key, value = line.split(":", 1) - info[key.replace(MAIL_HEADER_PREFIX, "")] = value.strip() + if ':' in line: + key, value = line.split(':', 1) + info[key.replace(MAIL_HEADER_PREFIX, '')] = value.strip() + if not info: continue - mark_as_read.append(m) - info["subject"] = m.subject + # Přidání předmětu a uložení do výsledného listu + info['subject'] = msg['Subject'] unread_messages.append(info) - mailbox.flag( - [m.uid for m in mark_as_read], - (imap_tools.MailMessageFlags.SEEN,), - True, - ) + # Odhlášení + mailbox.logout() return unread_messages + def _generate_oauth_2_string(self, username, access_token, base64_encode=True): + """Generates an IMAP OAuth2 authentication string. + + CODE FROM: https://github.com/google/gmail-oauth2-tools/tree/master + + See https://developers.google.com/google-apps/gmail/oauth2_overview + + Args: + username: the username (email address) of the account to authenticate + access_token: An OAuth2 access token. + base64_encode: Whether to base64-encode the output. + + Returns: + The SASL argument for the OAuth2 mechanism. + """ + auth_string = 'user=%s\1auth=Bearer %s\1\1' % (username, access_token) + if base64_encode: + auth_string = base64.b64encode(auth_string.encode('utf-8')) + return auth_string + + def _refresh_token(self, client_id, client_secret, refresh_token): + """Obtains a new token given a refresh token. + + CODE FROM: https://github.com/google/gmail-oauth2-tools/tree/master + + See https://developers.google.com/accounts/docs/OAuth2InstalledApp#refresh + + Args: + client_id: Client ID obtained by registering your app. + client_secret: Client secret obtained by registering your app. + refresh_token: A previously-obtained refresh token. + Returns: + The decoded response from the Google Accounts server, as a dict. Expected + fields include 'access_token', 'expires_in', and 'refresh_token'. + """ + params = {} + params['client_id'] = client_id + params['client_secret'] = client_secret + params['refresh_token'] = refresh_token + params['grant_type'] = 'refresh_token' + request_url = "https://accounts.google.com/o/oauth2/token" + + response = urllib.request.urlopen(request_url, urllib.parse.urlencode(params).encode('utf-8')).read() + return json.loads(response) + async def setup(bot) -> None: await bot.add_cog(Verify(bot))