Skip to content

Commit 3d36034

Browse files
authored
Merge pull request #428 from AsmodaiP/fix-email-widget
Fix email widget
2 parents 6f80b53 + c2acfdd commit 3d36034

File tree

9 files changed

+189
-77
lines changed

9 files changed

+189
-77
lines changed

email-widget/.env.example

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
EMAIL=
2+
PASSWORD=
3+
MAX_MSG_COUNT=
4+
MAX_BODY_LENGTH=
5+

email-widget/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.venv
2+
.env

email-widget/README.md

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,34 +3,52 @@
33
This widget consists of an icon with counter which shows number of unread emails: ![email icon](./em-wid-1.png)
44
and a popup message which appears when mouse hovers over an icon: ![email popup](./em-wid-2.png)
55

6-
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.
7-
86
## Installation
7+
1. Clone this repository to your awesome config folder:
98

10-
To install it put **email.lua** and **email-widget** folder under **~/.config/awesome**. Then
9+
```bash
10+
git clone https://github.com/streetturtle/awesome-wm-widgets/email-widget ~/.config/awesome/email-widget
11+
```
12+
2. Make virtual environment and install dependencies:
1113

12-
- in **email.lua** change path to python scripts;
13-
- in python scripts add your credentials (note that password should be encrypted using pgp for example);
14-
- add widget to awesome:
14+
```bash
15+
cd ~/.config/awesome/email-widget
16+
python3 -m venv venv
17+
source venv/bin/activate
18+
pip install -r requirements.txt
19+
```
20+
3. Fill .env file with your credentials:
1521

16-
```lua
17-
local email_widget, email_icon = require("email")
22+
```bash
23+
cp .env.example .env
24+
```
25+
4. Add widget to awesome:
1826

27+
```lua
28+
local email_widget = require("email-widget.email")
1929
...
2030
s.mytasklist, -- Middle widget
2131
{ -- Right widgets
22-
layout = wibox.layout.fixed.horizontal,
32+
layout = wibox.layout.fixed.horizontal,
2333
...
24-
email_icon,
25-
email_widget,
34+
email_widget,
2635
...
2736
```
2837

38+
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.
39+
If you want to configure width of popup window, you can change this line in email.lua file:
40+
41+
```lua
42+
width = 800,
43+
```
44+
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.
45+
Next step is restarting awesome. You can do this by pressing Mod+Ctrl+r.
46+
2947
## How it works
3048

3149
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:
3250

3351
``` bash
34-
python ~/.config/awesome/email/count_unread_emails.py
35-
python ~/.config/awesome/email/read_emails.py
52+
python ~/.config/awesome/email-widget/count_unread_emails.py
53+
python ~/.config/awesome/email-widget/read_emails.py
3654
```
Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,25 @@
1-
#!/usr/bin/python
2-
31
import imaplib
42
import re
3+
from dotenv import load_dotenv
4+
from pathlib import Path
5+
import os
6+
path_to_env = Path(__file__).parent / '.env'
7+
load_dotenv(path_to_env)
8+
EMAIL = os.getenv("EMAIL")
9+
PASSWORD = os.getenv("PASSWORD")
10+
if not EMAIL or not PASSWORD:
11+
print("ERROR:Email or password not set in .env file.")
12+
exit(0)
13+
514

6-
M=imaplib.IMAP4_SSL("mail.teenagemutantninjaturtles.com", 993)
7-
M.login("[email protected]","cowabunga")
15+
M = imaplib.IMAP4_SSL("imap.gmail.com", 993)
16+
M.login(EMAIL, PASSWORD)
817

9-
status, counts = M.status("INBOX","(MESSAGES UNSEEN)")
18+
status, counts = M.status("INBOX", "(MESSAGES UNSEEN)")
1019

1120
if status == "OK":
12-
unread = re.search(r'UNSEEN\s(\d+)', counts[0].decode('utf-8')).group(1)
21+
unread = re.search(r"UNSEEN\s(\d+)", counts[0].decode("utf-8")).group(1)
1322
else:
14-
unread = "N/A"
23+
unread = "N/A"
1524

1625
print(unread)

email-widget/em-wid-1.png

-981 Bytes
Loading

email-widget/em-wid-2.png

-26.1 KB
Loading

email-widget/email.lua

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,42 +3,47 @@ local awful = require("awful")
33
local naughty = require("naughty")
44
local watch = require("awful.widget.watch")
55

6-
local path_to_icons = "/usr/share/icons/Arc/actions/22/"
6+
local currentPath = debug.getinfo(1, "S").source:sub(2):match("(.*/)")
77

88
local email_widget = wibox.widget.textbox()
99
email_widget:set_font('Play 9')
10+
email_widget:set_text("Loading...")
1011

11-
local email_icon = wibox.widget.imagebox()
12-
email_icon:set_image(path_to_icons .. "/mail-mark-new.png")
12+
13+
path_to_python_in_venv = currentPath .. "/.venv/bin/python"
1314

1415
watch(
15-
"python /home/<username>/.config/awesome/email-widget/count_unread_emails.py", 20,
16-
function(_, stdout)
16+
path_to_python_in_venv.." "..currentPath.."count_unread_emails.py", 20, function(_, stdout)
17+
local is_error = (stdout:match("ERROR") ~= nil)
18+
email_widget:set_text("status: "..tostring(is_error))
19+
if is_error then
20+
email_widget:set_text(stdout)
21+
return
22+
end
1723
local unread_emails_num = tonumber(stdout) or 0
1824
if (unread_emails_num > 0) then
19-
email_icon:set_image(path_to_icons .. "/mail-mark-unread.png")
20-
email_widget:set_text(stdout)
25+
email_widget:set_text(unread_emails_num)
2126
elseif (unread_emails_num == 0) then
22-
email_icon:set_image(path_to_icons .. "/mail-message-new.png")
2327
email_widget:set_text("")
2428
end
2529
end
2630
)
27-
28-
2931
local function show_emails()
30-
awful.spawn.easy_async([[bash -c 'python /home/<username>/.config/awesome/email-widget/read_unread_emails.py']],
31-
function(stdout)
32+
awful.spawn.easy_async(
33+
path_to_python_in_venv.." "..currentPath.."read_unread_emails.py",
34+
function(stdout)
3235
naughty.notify{
3336
text = stdout,
3437
title = "Unread Emails",
35-
timeout = 5, hover_timeout = 0.5,
36-
width = 400,
38+
timeout = 10, hover_timeout = 0.5,
39+
width = 800,
40+
parse = true,
3741
}
3842
end
3943
)
4044
end
4145

42-
email_icon:connect_signal("mouse::enter", function() show_emails() end)
46+
email_widget:connect_signal("mouse::enter", function() show_emails() end)
4347

44-
return email_widget, email_icon
48+
-- show_emails()
49+
return email_widget

email-widget/read_unread_emails.py

Lines changed: 112 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,115 @@
1-
#!/usr/bin/python
2-
31
import imaplib
42
import email
5-
import datetime
6-
7-
def process_mailbox(M):
8-
rv, data = M.search(None, "(UNSEEN)")
9-
if rv != 'OK':
10-
print "No messages found!"
11-
return
12-
13-
for num in data[0].split():
14-
rv, data = M.fetch(num, '(BODY.PEEK[])')
15-
if rv != 'OK':
16-
print "ERROR getting message", num
17-
return
18-
msg = email.message_from_bytes(data[0][1])
19-
for header in [ 'From', 'Subject', 'Date' ]:
20-
hdr = email.header.make_header(email.header.decode_header(msg[header]))
21-
if header == 'Date':
22-
date_tuple = email.utils.parsedate_tz(str(hdr))
23-
if date_tuple:
24-
local_date = datetime.datetime.fromtimestamp(email.utils.mktime_tz(date_tuple))
25-
print("{}: {}".format(header, local_date.strftime("%a, %d %b %Y %H:%M:%S")))
3+
import html2text
4+
import re
5+
from email.header import make_header, decode_header
6+
from pathlib import Path
7+
import os
8+
from dotenv import load_dotenv
9+
10+
path_to_env = Path(__file__).parent / '.env'
11+
load_dotenv(path_to_env)
12+
EMAIL = os.getenv("EMAIL")
13+
PASSWORD = os.getenv("PASSWORD")
14+
15+
MAX_MSG_COUNT = int(os.getenv("MAX_MSG_COUNT", 5))
16+
MAX_BODY_LENGTH = int(os.getenv("MAX_BODY_LENGTH", 100))
17+
18+
GREEN = "\033[1;32m"
19+
END_COLOR = "\033[0m"
20+
21+
UNSEEN_FLAG = "(UNSEEN)"
22+
BODY_PEEK_FLAG = "(BODY.PEEK[])"
23+
def colorful_text(text, color):
24+
"""
25+
Function to format text with Pango markup for color.
26+
"""
27+
return f"<span foreground='{color}'>{text}</span>"
28+
29+
def format_body(body, max_length=MAX_BODY_LENGTH):
30+
body = body.decode("utf-8", errors="ignore")
31+
32+
if "DOCTYPE" in body:
33+
body = html2text.html2text(body)
34+
body = body.replace("\n", "").replace("\r\n", "").replace(" ", "")
35+
body = re.sub(r"\[.*\]\(.*\)", "", body)
36+
37+
return body if len(body) < max_length else body[:max_length] + "..."
38+
39+
def get_short_email_str(M, num_emails=MAX_MSG_COUNT):
40+
rv, data = M.search(None, UNSEEN_FLAG)
41+
email_list = list(reversed(data[0].split()))[:num_emails]
42+
43+
for num in email_list:
44+
try:
45+
rv, data = M.fetch(num, BODY_PEEK_FLAG)
46+
if rv != "OK":
47+
print("ERROR getting message", num)
48+
continue
49+
50+
msg = email.message_from_bytes(data[0][1])
51+
52+
sender = make_header(decode_header(msg["From"]))
53+
subject = make_header(decode_header(msg["Subject"]))
54+
date = make_header(decode_header(msg["Date"]))
55+
56+
email_info = (
57+
f"From: {colorful_text(str(sender).replace('<', '').replace('>', ''), 'green')}\n"
58+
f"Subject: {colorful_text(str(subject), 'red')}\n"
59+
f"Date: {date}\n"
60+
)
61+
62+
if msg.is_multipart():
63+
for part in msg.walk():
64+
content_type = part.get_content_type()
65+
content_disposition = str(part.get("Content-Disposition"))
66+
67+
if (
68+
content_type == "text/plain"
69+
and "attachment" not in content_disposition
70+
):
71+
body = part.get_payload(decode=True)
72+
email_info += format_body(body)
73+
break
74+
elif (
75+
content_type == "text/html"
76+
and "attachment" not in content_disposition
77+
):
78+
body = part.get_payload(decode=True)
79+
body = html2text.html2text(
80+
body.decode("utf-8", errors="ignore")
81+
)
82+
email_info += format_body(body)
83+
break
2684
else:
27-
print('{}: {}'.format(header, hdr))
28-
# with code below you can process text of email
29-
# if msg.is_multipart():
30-
# for payload in msg.get_payload():
31-
# if payload.get_content_maintype() == 'text':
32-
# print payload.get_payload()
33-
# else:
34-
# print msg.get_payload()
35-
36-
37-
M=imaplib.IMAP4_SSL("mail.teenagemutantninjaturtles.com", 993)
38-
M.login("[email protected]","cowabunga")
39-
40-
rv, data = M.select("INBOX")
41-
if rv == 'OK':
42-
process_mailbox(M)
43-
M.close()
44-
M.logout()
85+
body = msg.get_payload(decode=True)
86+
email_info += format_body(body)
87+
88+
email_info += "\n" + "=" * 50 + "\n"
89+
yield email_info
90+
91+
except Exception:
92+
print("ERROR processing message: ", num)
93+
94+
if __name__ == "__main__":
95+
# Example usage:
96+
# read_unread_emails.py
97+
import time
98+
start = time.time()
99+
100+
M = imaplib.IMAP4_SSL("imap.gmail.com", 993)
101+
try:
102+
M.login(EMAIL, PASSWORD)
103+
rv, data = M.select("INBOX")
104+
105+
if rv == "OK":
106+
for email_str in get_short_email_str(M, MAX_MSG_COUNT):
107+
print(email_str)
108+
else:
109+
print("Error selecting INBOX")
110+
except Exception:
111+
print("Error logging in: ", EMAIL)
112+
finally:
113+
M.logout()
114+
115+
print(f"Time taken: {time.time() - start:.2f} seconds")

email-widget/requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
html2text==2020.1.16
2+
python-dotenv==1.0.0

0 commit comments

Comments
 (0)