Skip to content

Commit

Permalink
Merge pull request #428 from AsmodaiP/fix-email-widget
Browse files Browse the repository at this point in the history
Fix email widget
  • Loading branch information
streetturtle authored Dec 7, 2023
2 parents 6f80b53 + c2acfdd commit 3d36034
Show file tree
Hide file tree
Showing 9 changed files with 189 additions and 77 deletions.
5 changes: 5 additions & 0 deletions email-widget/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
EMAIL=
PASSWORD=
MAX_MSG_COUNT=
MAX_BODY_LENGTH=

2 changes: 2 additions & 0 deletions email-widget/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.venv
.env
44 changes: 31 additions & 13 deletions email-widget/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

```lua
local email_widget, email_icon = require("email")
```bash
cp .env.example .env
```
4. Add widget to awesome:

```lua
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
```
23 changes: 16 additions & 7 deletions email-widget/count_unread_emails.py
Original file line number Diff line number Diff line change
@@ -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("[email protected]","cowabunga")
M = imaplib.IMAP4_SSL("imap.gmail.com", 993)
M.login(EMAIL, PASSWORD)

status, counts = M.status("INBOX","(MESSAGES UNSEEN)")
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 modified email-widget/em-wid-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified email-widget/em-wid-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
37 changes: 21 additions & 16 deletions email-widget/email.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
153 changes: 112 additions & 41 deletions email-widget/read_unread_emails.py
Original file line number Diff line number Diff line change
@@ -1,44 +1,115 @@
#!/usr/bin/python

import imaplib
import email
import datetime

def process_mailbox(M):
rv, data = M.search(None, "(UNSEEN)")
if rv != 'OK':
print "No messages found!"
return

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")))
import html2text
import re
from email.header import make_header, decode_header
from pathlib import Path
import os
from dotenv import load_dotenv

path_to_env = Path(__file__).parent / '.env'
load_dotenv(path_to_env)
EMAIL = os.getenv("EMAIL")
PASSWORD = os.getenv("PASSWORD")

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()


M=imaplib.IMAP4_SSL("mail.teenagemutantninjaturtles.com", 993)
M.login("[email protected]","cowabunga")

rv, data = M.select("INBOX")
if rv == 'OK':
process_mailbox(M)
M.close()
M.logout()
body = msg.get_payload(decode=True)
email_info += format_body(body)

email_info += "\n" + "=" * 50 + "\n"
yield email_info

except Exception:
print("ERROR processing message: ", num)

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")
2 changes: 2 additions & 0 deletions email-widget/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
html2text==2020.1.16
python-dotenv==1.0.0

0 comments on commit 3d36034

Please sign in to comment.