aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhyfc <[email protected]>2018-06-10 23:27:45 +0800
committerhyfc <[email protected]>2020-10-22 11:37:35 +0800
commit9d71b348da48ac3c9c5da2e36c8741a7bd38ef1a (patch)
tree4374148dbd08af11e87642db928a42d46e88b961
parent0d46d715374b51bd4a0833e7687ae33ae46376b3 (diff)
downloadtelegram-mail-bot-9d71b348da48ac3c9c5da2e36c8741a7bd38ef1a.tar.gz
telegram-mail-bot-9d71b348da48ac3c9c5da2e36c8741a7bd38ef1a.tar.bz2
telegram-mail-bot-9d71b348da48ac3c9c5da2e36c8741a7bd38ef1a.zip
a first working verison
-rw-r--r--.gitignore6
-rw-r--r--Pipfile13
-rw-r--r--Pipfile.lock50
-rw-r--r--Procfile1
-rw-r--r--bot.py101
-rw-r--r--utils/mail.py128
6 files changed, 287 insertions, 12 deletions
diff --git a/.gitignore b/.gitignore
index 894a44c..11ddee2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -102,3 +102,9 @@ venv.bak/
# mypy
.mypy_cache/
+
+#IDEA files
+.idea/
+
+#vscode files
+.vscode/ \ No newline at end of file
diff --git a/Pipfile b/Pipfile
new file mode 100644
index 0000000..36dd8a2
--- /dev/null
+++ b/Pipfile
@@ -0,0 +1,13 @@
+[[source]]
+url = "https://pypi.org/simple"
+verify_ssl = true
+name = "pypi"
+
+[packages]
+pytz = "*"
+python-telegram-bot = "*"
+
+[dev-packages]
+
+[requires]
+python_version = "3.6"
diff --git a/Pipfile.lock b/Pipfile.lock
new file mode 100644
index 0000000..49e9354
--- /dev/null
+++ b/Pipfile.lock
@@ -0,0 +1,50 @@
+{
+ "_meta": {
+ "hash": {
+ "sha256": "f7da4ab6099df3aff82e024a8a850f8e6606a3ee0ecdbbc55e20e22d40864c5e"
+ },
+ "pipfile-spec": 6,
+ "requires": {
+ "python_version": "3.6"
+ },
+ "sources": [
+ {
+ "name": "pypi",
+ "url": "https://pypi.org/simple",
+ "verify_ssl": true
+ }
+ ]
+ },
+ "default": {
+ "certifi": {
+ "hashes": [
+ "sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7",
+ "sha256:9fa520c1bacfb634fa7af20a76bcbd3d5fb390481724c597da32c719a7dca4b0"
+ ],
+ "version": "==2018.4.16"
+ },
+ "future": {
+ "hashes": [
+ "sha256:e39ced1ab767b5936646cedba8bcce582398233d6a627067d4c6a454c90cfedb"
+ ],
+ "version": "==0.16.0"
+ },
+ "python-telegram-bot": {
+ "hashes": [
+ "sha256:725a28f77a04d056559015963a21eacf5d2d1f1722192237284828b7cc437465",
+ "sha256:ca2f8a44ddef7271477e16f4986647fa90ef4df5b55a7953e53b9c9d2672f639"
+ ],
+ "index": "pypi",
+ "version": "==10.1.0"
+ },
+ "pytz": {
+ "hashes": [
+ "sha256:65ae0c8101309c45772196b21b74c46b2e5d11b6275c45d251b150d5da334555",
+ "sha256:c06425302f2cf668f1bba7a0a03f3c1d34d4ebeef2c72003da308b3947c7f749"
+ ],
+ "index": "pypi",
+ "version": "==2018.4"
+ }
+ },
+ "develop": {}
+}
diff --git a/Procfile b/Procfile
new file mode 100644
index 0000000..c22255a
--- /dev/null
+++ b/Procfile
@@ -0,0 +1 @@
+bot: python3 bot.py \ No newline at end of file
diff --git a/bot.py b/bot.py
index e69de29..bda0dfd 100644
--- a/bot.py
+++ b/bot.py
@@ -0,0 +1,101 @@
+import logging
+import os
+
+from utils.mail import EmailClient
+from telegram import ParseMode
+from telegram.constants import MAX_MESSAGE_LENGTH
+from telegram.ext import (Updater, CommandHandler)
+
+
+logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
+ level=logging.INFO)
+logger = logging.getLogger(__name__)
+
+bot_token = os.environ['TELEGRAM_TOKEN']
+
+
+def error(bot, update, _error):
+ """Log Errors caused by Updates."""
+ logger.warning('Update "%s" caused error "%s"', update, _error)
+
+def start_callback(bot, update):
+ msg = "Use /help to get help"
+ update.message.reply_text(msg)
+
+def _help(bot, update):
+ """Send a message when the command /help is issued."""
+ help_str = "*Mailbox Setting*: \n" \
+ "/setting [email protected] yourpassword"
+ bot.send_message(update.message.chat_id,
+ parse_mode=ParseMode.MARKDOWN,
+ text=help_str)
+
+def setting_email(bot, update, args):
+ global email_addr, email_passwd
+ email_addr = args[0]
+ email_passwd = args[1]
+
+ update.message.reply_text("Configure email success!")
+
+def inbox(bot, update):
+ with EmailClient(email_addr, email_passwd) as client:
+ num_of_mails = client.get_mails_count()
+ reply_text = "The index of newest mail is *%d*" % num_of_mails
+ bot.send_message(update.message.chat_id,
+ parse_mode=ParseMode.MARKDOWN,
+ text=reply_text)
+
+def get_email(bot, update, args):
+ index = args[0]
+ with EmailClient(email_addr, email_passwd) as client:
+ mail = client.get_mail_by_index(index)
+ subject = "*Subject*: %s\n" % mail.subject
+ sender = "*From*: %s - %s\n" % (mail.from_nickname, mail.from_account)
+ date = "*Date*: %s\n" % mail.receivedtime
+ bot.send_message(update.message.chat_id,
+ parse_mode=ParseMode.MARKDOWN,
+ text=subject+sender+date)
+ if len(mail.text_content) > MAX_MESSAGE_LENGTH:
+ text = mail.text_content[0:4096]
+ bot.send_message(update.message.chat_id,
+ text=text)
+ mail.text_content = mail.text_content.lstrip(text)
+ if mail.text_content:
+ bot.send_message(update.message.chat_id,
+ text=mail.text_content)
+
+
+
+def main():
+ # Create the EventHandler and pass it your bot's token.
+ updater = Updater(token=bot_token)
+
+ # Get the dispatcher to register handlers
+ dp = updater.dispatcher
+
+ # simple start function
+ dp.add_handler(CommandHandler("start", start_callback))
+
+ dp.add_handler(CommandHandler("help", _help))
+ #
+ # Add command handler to set email address and account.
+ dp.add_handler(CommandHandler("setting", setting_email, pass_args=True))
+
+ dp.add_handler(CommandHandler("inbox", inbox))
+
+ dp.add_handler(CommandHandler("get", get_email, pass_args=True))
+
+
+ dp.add_error_handler(error)
+
+ # Start the Bot
+ updater.start_polling()
+
+ # Run the bot until you press Ctrl-C or the process receives SIGINT,
+ # SIGTERM or SIGABRT. This should be used most of the time, since
+ # start_polling() is non-blocking and will stop the bot gracefully.
+ updater.idle()
+
+
+if __name__ == '__main__':
+ main() \ No newline at end of file
diff --git a/utils/mail.py b/utils/mail.py
index 3dc346f..f26e7b8 100644
--- a/utils/mail.py
+++ b/utils/mail.py
@@ -1,20 +1,97 @@
+import base64
+from datetime import datetime
+from email.parser import Parser
+import logging
import poplib
+import pytz
+import re
-class MailInfo(object):
- """
- Class for storing mail's metadata
- """
+logger = logging.getLogger(__name__)
+
+class MailDetails(object):
def __init__(self):
- self.index = 0
- self.size = 0
- self.status = ""
- self.data = ""
+ self.from_nickname = ""
+ self.from_account = ""
+ self.to_nickname = ""
+ self.to_account = ""
+ self.subject = ""
+ self.receivedtime = ""
+ self.text_content = ""
+ self.html_content = ""
+
+
+def decode_byte(bstr, charset='utf8'):
+ return bstr.decode(charset)
+
+def get_rawcontent_charset(rawcontent):
+ for item in rawcontent:
+ if decode_byte(item).find('charset='):
+ charset = re.findall(re.compile('charset="(.*)"'), decode_byte(item))
+ for member in charset:
+ if member is not None:
+ return member
+
+
+def parse_raw_mail_data(raw_lines, charset='utf8'):
+ msg_content = b'\r\n'.join(raw_lines).decode(encoding=charset)
+ return Parser().parsestr(text=msg_content)
+
+def decode_base64(s, charset='utf8'):
+ return str(base64.decodebytes(s.encode(encoding=charset)), encoding=charset)
+
+
+def get_mail_info(s):
+ try:
+ nickname, account = s.split(" ")
+ except ValueError:
+ nickname = ''
+ account = s
+
+ account = account.lstrip('<')
+ account = account.rstrip('>')
+ return nickname, account
+
+def get_mail_details(msg):
+ maildetails = MailDetails()
+
+ fromstr = msg.get('From')
+ from_nickname, from_account = get_mail_info(fromstr)
+ maildetails.from_nickname = from_nickname
+ maildetails.from_account = from_account
+ tostr = msg.get('To')
+ to_nickname, to_account = get_mail_info(tostr)
+ maildetails.to_nickname = to_nickname
+ maildetails.to_account = to_account
+
+ subject = msg.get('Subject')
+ try:
+ maildetails.subject = decode_base64(subject.split("?")[3], charset=subject.split("?")[1])
+ except IndexError:
+ maildetails.subject = subject
+ received_time = msg.get("Date")
+ time_str_fmt = "%a, %d %b %Y %H:%M:%S %z"
+ time_obj = datetime.strptime(received_time, time_str_fmt)
+ time_obj = time_obj.astimezone(pytz.timezone('Asia/Hong_Kong'))
+ maildetails.receivedtime = time_obj.strftime(time_str_fmt)
+
+ parts = msg.get_payload()
+ content_charset = parts[0].get_content_charset()
+ content = parts[0].as_string().split('base64')[-1]
+ try:
+ maildetails.text_content = decode_base64(content, content_charset)
+ except Exception as e:
+ logger.error('Exception caught: "%s"', e)
+ maildetails.text_content = content
+ content = parts[1].as_string().split('base64')[-1]
+ maildetails.html_content = content
+
+ return maildetails
class EmailClient(object):
- def __init__(self, email_account, password):
+ def __init__(self, email_account, passwd):
self.email_account = email_account
- self.password = password
+ self.password = passwd
self.server = self.connect(self)
@staticmethod
@@ -34,15 +111,42 @@ class EmailClient(object):
return server
- def get_mail_count(self):
+ def get_mails_list(self):
_, mails, _ = self.server.list()
+ return mails
+
+ def get_mails_count(self):
+ mails = self.get_mails_list()
return len(mails)
+ def get_mail_by_index(self, index):
+ resp_status, mail_lines, mail_octets = self.server.retr(index)
+ content_charset = get_rawcontent_charset(mail_lines)
+ data = parse_raw_mail_data(mail_lines, charset=content_charset or 'utf-8')
+ maildetails = get_mail_details(data)
+ return maildetails
+
+
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ if exc_type is None:
+ print('exited normally\n')
+ self.server.quit()
+ else:
+ print('raise an exception! ' + str(exc_type))
+ self.server.close()
+ return False # Propagate
+
+
+
if __name__ == '__main__':
useraccount = "XXXXX"
password = "XXXXXX"
client = EmailClient(useraccount, password)
- client.get_mail_count() \ No newline at end of file
+ client.get_mails_count() \ No newline at end of file