aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMole Shang <[email protected]>2023-01-25 04:10:29 +0800
committerMole Shang <[email protected]>2023-01-25 04:12:18 +0800
commitafa152d8c5ac8a74945612255f78cb4acbf84450 (patch)
tree5ae4900fe01f83398961209f7b091de951db3f1f
parent578759eed8ecdde831f0982784eec4b500555f37 (diff)
downloadtelegram-mail-bot-afa152d8c5ac8a74945612255f78cb4acbf84450.tar.gz
telegram-mail-bot-afa152d8c5ac8a74945612255f78cb4acbf84450.tar.bz2
telegram-mail-bot-afa152d8c5ac8a74945612255f78cb4acbf84450.zip
refactor: rewrite for IMAP support
- Replaced POP3 with IMAP - #TODO: custom server URL - Polish user prompts
-rw-r--r--bot.py78
-rw-r--r--utils/client.py51
-rw-r--r--utils/mail.py16
3 files changed, 84 insertions, 61 deletions
diff --git a/bot.py b/bot.py
index b562768..c533d51 100644
--- a/bot.py
+++ b/bot.py
@@ -3,22 +3,26 @@ import os
import sys
from telegram import ParseMode, Update
from telegram.constants import MAX_MESSAGE_LENGTH
-from telegram.ext import (Updater, CommandHandler, CallbackContext)
+from telegram.ext import Updater, CommandHandler, CallbackContext
from utils.client import EmailClient
-logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s:%(lineno)d - %(message)s',
- stream=sys.stdout,
- level=logging.INFO)
+logging.basicConfig(
+ format="%(asctime)s - %(name)s - %(levelname)s:%(lineno)d - %(message)s",
+ stream=sys.stdout,
+ level=logging.INFO,
+)
logger = logging.getLogger(__name__)
-bot_token = os.environ['TELEGRAM_TOKEN']
+bot_token = os.environ["TELEGRAM_TOKEN"]
+
+owner_chat_id = int(os.environ["OWNER_CHAT_ID"])
-owner_chat_id = int(os.environ['OWNER_CHAT_ID'])
def is_owner(update: Update) -> bool:
return update.message.chat_id == owner_chat_id
+
def handle_large_text(text):
while text:
if len(text) < MAX_MESSAGE_LENGTH:
@@ -29,10 +33,12 @@ def handle_large_text(text):
yield out
text = text.lstrip(out)
+
def error(update: Update, context: CallbackContext) -> None:
"""Log Errors caused by Updates."""
logger.warning('Update "%s" caused error "%s"', update, context.error)
+
def start_callback(update: Update, context: CallbackContext) -> None:
if not is_owner(update):
return
@@ -40,6 +46,7 @@ def start_callback(update: Update, context: CallbackContext) -> None:
# print(update)
update.message.reply_text(msg)
+
def _help(update: Update, context: CallbackContext) -> None:
if not is_owner(update):
return
@@ -53,9 +60,12 @@ def _help(update: Update, context: CallbackContext) -> None:
# "/setting [email protected] password\n" \
# "/inbox\n" \
# "/get mail_index"
- context.bot.send_message(update.message.chat_id,
- # parse_mode=ParseMode.MARKDOWN,
- text=help_str)
+ context.bot.send_message(
+ update.message.chat_id,
+ # parse_mode=ParseMode.MARKDOWN,
+ text=help_str,
+ )
+
def setting_email(update: Update, context: CallbackContext) -> None:
if not is_owner(update):
@@ -67,7 +77,9 @@ def setting_email(update: Update, context: CallbackContext) -> None:
update.message.reply_text("Configure email success!")
with EmailClient(email_addr, email_passwd) as client:
inbox_num = client.get_mails_count()
- context.job_queue.run_repeating(periodic_task, interval=60, context=update.message.chat_id)
+ context.job_queue.run_repeating(
+ periodic_task, interval=60, context=update.message.chat_id
+ )
# chat_data['job'] = job
logger.info("periodic task scheduled.")
@@ -76,14 +88,15 @@ def periodic_task(context: CallbackContext) -> None:
global inbox_num
logger.info("entering periodic task.")
with EmailClient(email_addr, email_passwd) as client:
- new_inbox_num = client.get_mails_count()
- if new_inbox_num > inbox_num:
- mail = client.get_mail_by_index(new_inbox_num)
- content = mail.__repr__()
- for text in handle_large_text(content):
- context.bot.send_message(context.job.context,
- text=text)
- inbox_num = new_inbox_num
+ new_unseen_num = client.get_mails_unseen_count()
+ if new_unseen_num:
+ for i in range(inbox_num, inbox_num + new_unseen_num + 1):
+ mail = client.get_mail_by_index(i)
+ content = mail.__repr__()
+ for text in handle_large_text(content):
+ context.bot.send_message(context.job.context, text=text)
+ inbox_num += new_unseen_num
+
def inbox(update: Update, context: CallbackContext) -> None:
if not is_owner(update):
@@ -92,26 +105,32 @@ def inbox(update: Update, context: CallbackContext) -> None:
with EmailClient(email_addr, email_passwd) as client:
global inbox_num
new_num = client.get_mails_count()
- reply_text = "The index of newest mail is *%d*," \
- " received *%d* new mails since last" \
- " time you checked." % \
- (new_num, new_num - inbox_num)
+ reply_text = (
+ "The index of newest mail is *%d*,"
+ " received *%d* new mails since last"
+ " time you checked." % (new_num, new_num - inbox_num)
+ )
inbox_num = new_num
- context.bot.send_message(update.message.chat_id,
- parse_mode=ParseMode.MARKDOWN,
- text=reply_text)
+ context.bot.send_message(
+ update.message.chat_id, parse_mode=ParseMode.MARKDOWN, text=reply_text
+ )
+
def get_email(update: Update, context: CallbackContext) -> None:
if not is_owner(update):
return
index = context.args[0]
+ if not index:
+ context.bot.send_message(
+ update.message.chat_id, text="$index should be a positive number!"
+ )
logger.info("received get command.")
with EmailClient(email_addr, email_passwd) as client:
mail = client.get_mail_by_index(index)
content = mail.__repr__()
for text in handle_large_text(content):
- context.bot.send_message(update.message.chat_id,
- text=text)
+ context.bot.send_message(update.message.chat_id, text=text)
+
def main():
# Create the EventHandler and pass it your bot's token.
@@ -133,7 +152,6 @@ def main():
dp.add_handler(CommandHandler("get", get_email))
-
dp.add_error_handler(error)
# Start the Bot
@@ -145,5 +163,5 @@ def main():
updater.idle()
-if __name__ == '__main__':
- main() \ No newline at end of file
+if __name__ == "__main__":
+ main()
diff --git a/utils/client.py b/utils/client.py
index 1dc9205..884a922 100644
--- a/utils/client.py
+++ b/utils/client.py
@@ -1,63 +1,66 @@
import logging
-import poplib
+from imapclient import IMAPClient
from utils.mail import Email
logger = logging.getLogger(__name__)
-
class EmailClient(object):
def __init__(self, email_account, passwd):
self.email_account = email_account
self.password = passwd
- self.server = self.connect(self)
+ self.server, self.number = self.connect(self)
@staticmethod
def connect(self):
# parse the server's hostname from email account
- pop3_server = 'pop.'+self.email_account.split('@')[-1]
- server = poplib.POP3_SSL(pop3_server)
+ imap4_server = "mx1." + self.email_account.split("@")[-1]
+ server = IMAPClient(host=imap4_server, use_uid=True)
# display the welcome info received from server,
# indicating the connection is set up properly
- logger.info(server.getwelcome().decode('utf8'))
+ logger.info(server.welcome)
# authenticating
- server.user(self.email_account)
- server.pass_(self.password)
- return server
+ server.login(self.email_account, self.password)
+ return server, server.select_folder("INBOX")[b"EXISTS"]
def get_mails_list(self):
- _, mails, _ = self.server.list()
- return mails
+ messages = self.server.search()
+ dict = self.server.fetch(messages, "RFC822")
+ return sorted(dict.items(), key=lambda e: e[0]) # Do resorts
def get_mails_count(self):
- mails = self.get_mails_list()
- return len(mails)
+ return self.number
+
+ def get_mails_unseen_count(self):
+ unseen_messages = self.server.search("UNSEEN")
+ return len(unseen_messages)
def get_mail_by_index(self, index):
- resp_status, mail_lines, mail_octets = self.server.retr(index)
- return Email(mail_lines)
+ list = self.get_mails_list()
+ msg_data = list[int(index)-1][1]
+ return Email(msg_data[b"RFC822"])
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is None:
- logger.info('exited normally\n')
- self.server.quit()
+ logger.info("exited normally\n")
+ self.server.shutdown()
else:
- logger.error('raise an exception! ' + str(exc_type))
- self.server.close()
- return False # Propagate
+ logger.error("raise an exception! " + str(exc_type))
+ self.server.logout()
+ return False # Propagate
-
-if __name__ == '__main__':
+if __name__ == "__main__":
useraccount = "XXXXX"
password = "XXXXXX"
client = EmailClient(useraccount, password)
num = client.get_mails_count()
print(num)
- for i in range(1, num):
- print(client.get_mail_by_index(i)) \ No newline at end of file
+
+ for uid, msg_data in client.get_mails_list().items():
+ print(client.get_mail_by_index(uid))
diff --git a/utils/mail.py b/utils/mail.py
index 491faa8..3df068b 100644
--- a/utils/mail.py
+++ b/utils/mail.py
@@ -1,18 +1,20 @@
from pyzmail import PyzMessage, decode_text
+
class Email(object):
def __init__(self, raw_mail_lines):
- msg_content = b'\r\n'.join(raw_mail_lines)
- msg = PyzMessage.factory(msg_content)
+ msg = PyzMessage.factory(raw_mail_lines)
self.subject = msg.get_subject()
- self.sender = msg.get_address('from')
- self.date = msg.get_decoded_header('date', '')
- self.id = msg.get_decoded_header('message-id', '')
+ self.sender = msg.get_address("from")
+ self.date = msg.get_decoded_header("date", "")
+ self.id = msg.get_decoded_header("message-id", "")
for mailpart in msg.mailparts:
- if mailpart.is_body=='text/plain':
- payload, used_charset=decode_text(mailpart.get_payload(), mailpart.charset, None)
+ if mailpart.is_body == "text/plain":
+ payload, used_charset = decode_text(
+ mailpart.get_payload(), mailpart.charset, None
+ )
self.charset = used_charset
self.text = payload
return