Fix email widget
This commit is contained in:
parent
4a807514c2
commit
c2acfddd09
|
@ -0,0 +1,5 @@
|
|||
EMAIL=
|
||||
PASSWORD=
|
||||
MAX_MSG_COUNT=
|
||||
MAX_BODY_LENGTH=
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
.venv
|
||||
.env
|
|
@ -3,34 +3,52 @@
|
|||
This widget consists of an icon with counter which shows number of unread emails: ![email icon](./em-wid-1.png)
|
||||
and a popup message which appears when mouse hovers over an icon: ![email popup](./em-wid-2.png)
|
||||
|
||||
Note that widget uses the Arc icon theme, so it should be [installed](https://github.com/horst3180/arc-icon-theme#installation) first under **/usr/share/icons/Arc/** folder.
|
||||
|
||||
## Installation
|
||||
1. Clone this repository to your awesome config folder:
|
||||
|
||||
To install it put **email.lua** and **email-widget** folder under **~/.config/awesome**. Then
|
||||
```bash
|
||||
git clone https://github.com/streetturtle/awesome-wm-widgets/email-widget ~/.config/awesome/email-widget
|
||||
```
|
||||
2. Make virtual environment and install dependencies:
|
||||
|
||||
- in **email.lua** change path to python scripts;
|
||||
- in python scripts add your credentials (note that password should be encrypted using pgp for example);
|
||||
- add widget to awesome:
|
||||
```bash
|
||||
cd ~/.config/awesome/email-widget
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
3. Fill .env file with your credentials:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
4. Add widget to awesome:
|
||||
|
||||
```lua
|
||||
local email_widget, email_icon = require("email")
|
||||
|
||||
local email_widget = require("email-widget.email")
|
||||
...
|
||||
s.mytasklist, -- Middle widget
|
||||
{ -- Right widgets
|
||||
layout = wibox.layout.fixed.horizontal,
|
||||
layout = wibox.layout.fixed.horizontal,
|
||||
...
|
||||
email_icon,
|
||||
email_widget,
|
||||
email_widget,
|
||||
...
|
||||
```
|
||||
|
||||
If you want to reduce time of getting emails, you can change maximum number of emails to be fetched in .env file. Default is 10.
|
||||
If you want to configure width of popup window, you can change this line in email.lua file:
|
||||
|
||||
```lua
|
||||
width = 800,
|
||||
```
|
||||
After this you can change MAX_BODY_LENGTH variable in .env file to change number of characters to be displayed in popup window. Default is 100.
|
||||
Next step is restarting awesome. You can do this by pressing Mod+Ctrl+r.
|
||||
|
||||
## How it works
|
||||
|
||||
This widget uses the output of two python scripts, first is called every 20 seconds - it returns number of unread emails and second is called when mouse hovers over an icon and displays content of those emails. For both of them you'll need to provide your credentials and imap server. For testing, they can simply be called from console:
|
||||
|
||||
``` bash
|
||||
python ~/.config/awesome/email/count_unread_emails.py
|
||||
python ~/.config/awesome/email/read_emails.py
|
||||
python ~/.config/awesome/email-widget/count_unread_emails.py
|
||||
python ~/.config/awesome/email-widget/read_emails.py
|
||||
```
|
||||
|
|
|
@ -1,16 +1,25 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
import imaplib
|
||||
import re
|
||||
from dotenv import load_dotenv
|
||||
from pathlib import Path
|
||||
import os
|
||||
path_to_env = Path(__file__).parent / '.env'
|
||||
load_dotenv(path_to_env)
|
||||
EMAIL = os.getenv("EMAIL")
|
||||
PASSWORD = os.getenv("PASSWORD")
|
||||
if not EMAIL or not PASSWORD:
|
||||
print("ERROR:Email or password not set in .env file.")
|
||||
exit(0)
|
||||
|
||||
M=imaplib.IMAP4_SSL("mail.teenagemutantninjaturtles.com", 993)
|
||||
M.login("mickey@tmnt.com","cowabunga")
|
||||
|
||||
status, counts = M.status("INBOX","(MESSAGES UNSEEN)")
|
||||
M = imaplib.IMAP4_SSL("imap.gmail.com", 993)
|
||||
M.login(EMAIL, PASSWORD)
|
||||
|
||||
status, counts = M.status("INBOX", "(MESSAGES UNSEEN)")
|
||||
|
||||
if status == "OK":
|
||||
unread = re.search(r'UNSEEN\s(\d+)', counts[0].decode('utf-8')).group(1)
|
||||
unread = re.search(r"UNSEEN\s(\d+)", counts[0].decode("utf-8")).group(1)
|
||||
else:
|
||||
unread = "N/A"
|
||||
unread = "N/A"
|
||||
|
||||
print(unread)
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 855 B |
Binary file not shown.
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 12 KiB |
|
@ -3,42 +3,47 @@ local awful = require("awful")
|
|||
local naughty = require("naughty")
|
||||
local watch = require("awful.widget.watch")
|
||||
|
||||
local path_to_icons = "/usr/share/icons/Arc/actions/22/"
|
||||
local currentPath = debug.getinfo(1, "S").source:sub(2):match("(.*/)")
|
||||
|
||||
local email_widget = wibox.widget.textbox()
|
||||
email_widget:set_font('Play 9')
|
||||
email_widget:set_text("Loading...")
|
||||
|
||||
local email_icon = wibox.widget.imagebox()
|
||||
email_icon:set_image(path_to_icons .. "/mail-mark-new.png")
|
||||
|
||||
path_to_python_in_venv = currentPath .. "/.venv/bin/python"
|
||||
|
||||
watch(
|
||||
"python /home/<username>/.config/awesome/email-widget/count_unread_emails.py", 20,
|
||||
function(_, stdout)
|
||||
path_to_python_in_venv.." "..currentPath.."count_unread_emails.py", 20, function(_, stdout)
|
||||
local is_error = (stdout:match("ERROR") ~= nil)
|
||||
email_widget:set_text("status: "..tostring(is_error))
|
||||
if is_error then
|
||||
email_widget:set_text(stdout)
|
||||
return
|
||||
end
|
||||
local unread_emails_num = tonumber(stdout) or 0
|
||||
if (unread_emails_num > 0) then
|
||||
email_icon:set_image(path_to_icons .. "/mail-mark-unread.png")
|
||||
email_widget:set_text(stdout)
|
||||
email_widget:set_text(unread_emails_num)
|
||||
elseif (unread_emails_num == 0) then
|
||||
email_icon:set_image(path_to_icons .. "/mail-message-new.png")
|
||||
email_widget:set_text("")
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
|
||||
local function show_emails()
|
||||
awful.spawn.easy_async([[bash -c 'python /home/<username>/.config/awesome/email-widget/read_unread_emails.py']],
|
||||
function(stdout)
|
||||
awful.spawn.easy_async(
|
||||
path_to_python_in_venv.." "..currentPath.."read_unread_emails.py",
|
||||
function(stdout)
|
||||
naughty.notify{
|
||||
text = stdout,
|
||||
title = "Unread Emails",
|
||||
timeout = 5, hover_timeout = 0.5,
|
||||
width = 400,
|
||||
timeout = 10, hover_timeout = 0.5,
|
||||
width = 800,
|
||||
parse = true,
|
||||
}
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
email_icon:connect_signal("mouse::enter", function() show_emails() end)
|
||||
email_widget:connect_signal("mouse::enter", function() show_emails() end)
|
||||
|
||||
return email_widget, email_icon
|
||||
-- show_emails()
|
||||
return email_widget
|
|
@ -1,44 +1,115 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
import imaplib
|
||||
import email
|
||||
import datetime
|
||||
import html2text
|
||||
import re
|
||||
from email.header import make_header, decode_header
|
||||
from pathlib import Path
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
||||
def process_mailbox(M):
|
||||
rv, data = M.search(None, "(UNSEEN)")
|
||||
if rv != 'OK':
|
||||
print "No messages found!"
|
||||
return
|
||||
path_to_env = Path(__file__).parent / '.env'
|
||||
load_dotenv(path_to_env)
|
||||
EMAIL = os.getenv("EMAIL")
|
||||
PASSWORD = os.getenv("PASSWORD")
|
||||
|
||||
for num in data[0].split():
|
||||
rv, data = M.fetch(num, '(BODY.PEEK[])')
|
||||
if rv != 'OK':
|
||||
print "ERROR getting message", num
|
||||
return
|
||||
msg = email.message_from_bytes(data[0][1])
|
||||
for header in [ 'From', 'Subject', 'Date' ]:
|
||||
hdr = email.header.make_header(email.header.decode_header(msg[header]))
|
||||
if header == 'Date':
|
||||
date_tuple = email.utils.parsedate_tz(str(hdr))
|
||||
if date_tuple:
|
||||
local_date = datetime.datetime.fromtimestamp(email.utils.mktime_tz(date_tuple))
|
||||
print("{}: {}".format(header, local_date.strftime("%a, %d %b %Y %H:%M:%S")))
|
||||
MAX_MSG_COUNT = int(os.getenv("MAX_MSG_COUNT", 5))
|
||||
MAX_BODY_LENGTH = int(os.getenv("MAX_BODY_LENGTH", 100))
|
||||
|
||||
GREEN = "\033[1;32m"
|
||||
END_COLOR = "\033[0m"
|
||||
|
||||
UNSEEN_FLAG = "(UNSEEN)"
|
||||
BODY_PEEK_FLAG = "(BODY.PEEK[])"
|
||||
def colorful_text(text, color):
|
||||
"""
|
||||
Function to format text with Pango markup for color.
|
||||
"""
|
||||
return f"<span foreground='{color}'>{text}</span>"
|
||||
|
||||
def format_body(body, max_length=MAX_BODY_LENGTH):
|
||||
body = body.decode("utf-8", errors="ignore")
|
||||
|
||||
if "DOCTYPE" in body:
|
||||
body = html2text.html2text(body)
|
||||
body = body.replace("\n", "").replace("\r\n", "").replace(" ", "")
|
||||
body = re.sub(r"\[.*\]\(.*\)", "", body)
|
||||
|
||||
return body if len(body) < max_length else body[:max_length] + "..."
|
||||
|
||||
def get_short_email_str(M, num_emails=MAX_MSG_COUNT):
|
||||
rv, data = M.search(None, UNSEEN_FLAG)
|
||||
email_list = list(reversed(data[0].split()))[:num_emails]
|
||||
|
||||
for num in email_list:
|
||||
try:
|
||||
rv, data = M.fetch(num, BODY_PEEK_FLAG)
|
||||
if rv != "OK":
|
||||
print("ERROR getting message", num)
|
||||
continue
|
||||
|
||||
msg = email.message_from_bytes(data[0][1])
|
||||
|
||||
sender = make_header(decode_header(msg["From"]))
|
||||
subject = make_header(decode_header(msg["Subject"]))
|
||||
date = make_header(decode_header(msg["Date"]))
|
||||
|
||||
email_info = (
|
||||
f"From: {colorful_text(str(sender).replace('<', '').replace('>', ''), 'green')}\n"
|
||||
f"Subject: {colorful_text(str(subject), 'red')}\n"
|
||||
f"Date: {date}\n"
|
||||
)
|
||||
|
||||
if msg.is_multipart():
|
||||
for part in msg.walk():
|
||||
content_type = part.get_content_type()
|
||||
content_disposition = str(part.get("Content-Disposition"))
|
||||
|
||||
if (
|
||||
content_type == "text/plain"
|
||||
and "attachment" not in content_disposition
|
||||
):
|
||||
body = part.get_payload(decode=True)
|
||||
email_info += format_body(body)
|
||||
break
|
||||
elif (
|
||||
content_type == "text/html"
|
||||
and "attachment" not in content_disposition
|
||||
):
|
||||
body = part.get_payload(decode=True)
|
||||
body = html2text.html2text(
|
||||
body.decode("utf-8", errors="ignore")
|
||||
)
|
||||
email_info += format_body(body)
|
||||
break
|
||||
else:
|
||||
print('{}: {}'.format(header, hdr))
|
||||
# with code below you can process text of email
|
||||
# if msg.is_multipart():
|
||||
# for payload in msg.get_payload():
|
||||
# if payload.get_content_maintype() == 'text':
|
||||
# print payload.get_payload()
|
||||
# else:
|
||||
# print msg.get_payload()
|
||||
body = msg.get_payload(decode=True)
|
||||
email_info += format_body(body)
|
||||
|
||||
email_info += "\n" + "=" * 50 + "\n"
|
||||
yield email_info
|
||||
|
||||
M=imaplib.IMAP4_SSL("mail.teenagemutantninjaturtles.com", 993)
|
||||
M.login("mickey@tmnt.com","cowabunga")
|
||||
except Exception:
|
||||
print("ERROR processing message: ", num)
|
||||
|
||||
rv, data = M.select("INBOX")
|
||||
if rv == 'OK':
|
||||
process_mailbox(M)
|
||||
M.close()
|
||||
M.logout()
|
||||
if __name__ == "__main__":
|
||||
# Example usage:
|
||||
# read_unread_emails.py
|
||||
import time
|
||||
start = time.time()
|
||||
|
||||
M = imaplib.IMAP4_SSL("imap.gmail.com", 993)
|
||||
try:
|
||||
M.login(EMAIL, PASSWORD)
|
||||
rv, data = M.select("INBOX")
|
||||
|
||||
if rv == "OK":
|
||||
for email_str in get_short_email_str(M, MAX_MSG_COUNT):
|
||||
print(email_str)
|
||||
else:
|
||||
print("Error selecting INBOX")
|
||||
except Exception:
|
||||
print("Error logging in: ", EMAIL)
|
||||
finally:
|
||||
M.logout()
|
||||
|
||||
print(f"Time taken: {time.time() - start:.2f} seconds")
|
|
@ -0,0 +1,2 @@
|
|||
html2text==2020.1.16
|
||||
python-dotenv==1.0.0
|
Loading…
Reference in New Issue