-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Show new message notifications, add support for Waybar
- Loading branch information
Showing
16 changed files
with
429 additions
and
165 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
credentials.json | ||
/dist |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,67 +1,97 @@ | ||
# Polybar Gmail | ||
|
||
A [Polybar](https://github.com/jaagr/polybar) module to show unread messages from Gmail. | ||
# Bar Gmail | ||
|
||
![preview](https://github.com/crabvk/polybar-gmail/raw/master/preview.png) | ||
|
||
Get notifications and unread messages count from Gmail (Waybar/Polybar module). | ||
|
||
## Dependencies | ||
|
||
```sh | ||
pip install --upgrade google-api-python-client google-auth-httplib2 google-auth-oauthlib | ||
# or use poetry | ||
``` | ||
* Font Awesome: default badge | ||
* Libnotify: new email notifications, can be disabled with `--no-notify` flag. | ||
* Libcanberra: notification sound (optional). | ||
|
||
To display notifications you must have a [notification daemon](https://wiki.archlinux.org/title/Desktop_notifications#Notification_servers) running on your system. | ||
|
||
**Font Awesome** - default email icon | ||
## Install | ||
|
||
**canberra-gtk-play** - new email sound notification | ||
### ArchLinux and derivatives | ||
|
||
You can change the icon or turn off sound, for more info see [script arguments](#script-arguments) | ||
[AUR package](https://aur.archlinux.org/packages/bar-gmail/) | ||
|
||
## Installation | ||
### Other distros | ||
|
||
```sh | ||
cd ~/.config/polybar | ||
curl -LO https://github.com/crabvk/polybar-gmail/archive/master.tar.gz | ||
tar zxf master.tar.gz && rm master.tar.gz | ||
mv polybar-gmail-master gmail | ||
git clone https://github.com/crabvk/bar-gmail.git | ||
cd bar-gmail | ||
git describe --abbrev=0 --tags # Get latest tag. | ||
git checkoug LATEST_TAG | ||
pip install -e . | ||
``` | ||
|
||
and obtain/refresh credentials | ||
And now you can execute *~/.local/bin/bar-gmail* | ||
|
||
```sh | ||
~/.config/polybar/gmail/auth.py | ||
``` | ||
## Usage | ||
|
||
### Module | ||
First, you need to authenticate the client: | ||
|
||
```ini | ||
[module/gmail] | ||
type = custom/script | ||
exec = ~/.config/polybar/gmail/launch.py | ||
tail = true | ||
click-left = xdg-open https://mail.google.com | ||
```sh | ||
bar-gmail auth | ||
``` | ||
|
||
## Script arguments | ||
Then just run `bar-gmail` or `bar-gmail --format polybar` periodically to get unread messages count and new message notifications. | ||
Credentials and session are stored in *~/.cache/bar-gmail*. | ||
|
||
`-l` or `--label` - set user's mailbox [label](https://developers.google.com/gmail/api/v1/reference/users/labels/list), default: INBOX | ||
## Waybar config example | ||
|
||
`-p` or `--prefix` - set email icon, default: | ||
*~/.config/waybar/config* | ||
|
||
`-c` or `--color` - set new email icon color, default: #e06c75 | ||
```json | ||
"modules-right": { | ||
"custom/gmail" | ||
} | ||
|
||
`-ns` or `--nosound` - turn off new email sound | ||
"custom/gmail": { | ||
"exec": "bar-gmail", | ||
"return-type": "json", | ||
"interval": 10, | ||
"tooltip": false, | ||
"on-click": "xdg-open https://mail.google.com/mail/u/0/#inbox" | ||
} | ||
``` | ||
|
||
`-cr` or `--credentials` - path to your `credentials.json`, defaults to `credentials.json` | ||
*~/.config/waybar/style.css* | ||
|
||
```css | ||
#custom-gmail.unread { | ||
color: white; | ||
} | ||
#custom-gmail.inaccurate { | ||
color: darkorange; | ||
} | ||
#custom-gmail.error { | ||
color: darkred; | ||
} | ||
``` | ||
|
||
### Example | ||
## Polybar config example | ||
|
||
```sh | ||
./launch.py --label 'CATEGORY_PERSONAL' --prefix '✉' --color '#be5046' --nosound | ||
```ini | ||
modules-right = gmail | ||
... | ||
[module/gmail] | ||
type = custom/script | ||
exec = bar-gmail -f polybar | ||
interval = 10 | ||
click-left = xdg-open https://mail.google.com/mail/u/0/#inbox | ||
``` | ||
|
||
## Get list of all your mailbox labels | ||
## Script arguments | ||
|
||
```python | ||
./list_labels.py | ||
See `bar-gmail --help` for the full list of available subcommands and command arguments. | ||
Possible values for `-s`, `--sound` can be obtained with: | ||
|
||
```shell | ||
ls /usr/share/sounds/freedesktop/stereo/ | cut -d. -f1 | ||
``` | ||
|
||
for example `bar-gmail --sound message-new-instant`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import sys | ||
import argparse | ||
from pathlib import Path | ||
from bar_gmail.gmail import Gmail | ||
from bar_gmail.app import Application, UrgencyLevel | ||
from bar_gmail.printer import WaybarPrinter, PolybarPrinter | ||
|
||
|
||
def cli(): | ||
parser = argparse.ArgumentParser() | ||
subparsers = parser.add_subparsers(dest='subcommand') | ||
subparsers.add_parser('auth', help='Authentication.') | ||
subparsers.add_parser('labels', help='List mailbox labels.') | ||
parser.add_argument('-f', '--format', choices=['waybar', 'polybar'], default='waybar', | ||
help='Print output in specified format [default: waybar].') | ||
parser.add_argument('-b', '--badge', default='', | ||
help='Badge to display in the bar [default: ].') | ||
parser.add_argument('-c', '--color', | ||
help='Text foreground color (only for Polybar).') | ||
parser.add_argument('-l', '--label', default='INBOX', | ||
help="User's mailbox label for unread messages count [default: INBOX].") | ||
parser.add_argument('-s', '--sound', | ||
help='Notification sound (event sound ID from canberra-gtk-play).') | ||
parser.add_argument('-u', '--urgency', choices=['low', 'normal', 'critical'], | ||
default='normal', help='Notification urgency level [default: normal].') | ||
parser.add_argument('-t', '--expire-time', type=int, | ||
help='The duration, in milliseconds, for the notification to appear on screen.') | ||
parser.add_argument('-dn', '--no-notify', action='store_true', | ||
help='Disable new email notifications.') | ||
args = parser.parse_args() | ||
|
||
if args.color is not None and args.format != 'polybar': | ||
parser.error('`--color COLOR` can be used only with `--format polybar`.') | ||
|
||
BASE_DIR = Path(__file__).resolve().parent | ||
CLIENT_SECRETS_PATH = Path(BASE_DIR, 'client_secrets.json') | ||
CACHE_DIR = Path(Path.home(), '.cache/bar-gmail') | ||
CREDENTIALS_PATH = Path(CACHE_DIR, 'credentials.json') | ||
SESSION_PATH = Path(CACHE_DIR, 'session.json') | ||
|
||
if not CACHE_DIR.is_dir(): | ||
CACHE_DIR.mkdir(exist_ok=True) | ||
|
||
if not CREDENTIALS_PATH.is_file(): | ||
print('Credentials not found. Run `bar-gmail auth` for authentication.', file=sys.stderr) | ||
exit(1) | ||
|
||
gmail = Gmail(CLIENT_SECRETS_PATH, CREDENTIALS_PATH) | ||
|
||
if args.subcommand == 'auth': | ||
if gmail.authenticate(): | ||
print('Authenticated successfully.') | ||
exit() | ||
|
||
if args.subcommand == 'labels': | ||
for label in gmail.get_labels(): | ||
print(label) | ||
exit() | ||
|
||
if args.format == 'waybar': | ||
printer = WaybarPrinter(badge=args.badge) | ||
elif args.format == 'polybar': | ||
printer = PolybarPrinter(badge=args.badge, color=args.color) | ||
|
||
app = Application(SESSION_PATH, gmail, printer, | ||
badge=args.badge, | ||
color=args.color, | ||
label=args.label, | ||
sound_id=args.sound, | ||
urgency_level=UrgencyLevel(args.urgency), | ||
expire_time=args.expire_time, | ||
is_notify=not args.no_notify) | ||
app.run() | ||
|
||
|
||
if __name__ == '__main__': | ||
cli() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import os | ||
import json | ||
import time | ||
from enum import Enum | ||
from pathlib import Path | ||
from subprocess import Popen | ||
from bar_gmail.gmail import Gmail | ||
from bar_gmail.printer import WaybarPrinter, PolybarPrinter | ||
from google.auth.exceptions import TransportError | ||
from googleapiclient.errors import HttpError | ||
|
||
BASE_DIR = Path(__file__).resolve().parent | ||
GMAIL_ICON_PATH = Path(BASE_DIR, 'gmail_icon.svg') | ||
|
||
|
||
class UrgencyLevel(Enum): | ||
LOW = 'low' | ||
NORMAL = 'normal' | ||
CRITICAL = 'critical' | ||
|
||
|
||
class Application: | ||
def __init__(self, session_path: Path, gmail: Gmail, printer: WaybarPrinter | PolybarPrinter, | ||
badge: str, color: str | None, label: str, sound_id: str, | ||
urgency_level: UrgencyLevel, expire_time: int, is_notify: bool): | ||
self.session_path = session_path | ||
self.gmail = gmail | ||
self.printer = printer | ||
self.badge = badge | ||
self.label = label | ||
self.sound_id = sound_id | ||
self.urgency_level = urgency_level | ||
self.expire_time = expire_time | ||
self.is_notify = is_notify | ||
self.color = color | ||
args = [] | ||
# Set application name. | ||
args.extend(('-a', 'Bar Gmail')) | ||
# Set category. | ||
args.extend(('-c', 'email.arrived')) | ||
# Set icon. | ||
args.extend(('-i', GMAIL_ICON_PATH)) | ||
# Set urgency level. | ||
args.extend(('-u', self.urgency_level.value)) | ||
# Set notification expiration time. | ||
if self.expire_time is not None: | ||
args.extend(('-t', self.expire_time)) | ||
self.notification_args = args | ||
|
||
@staticmethod | ||
def _is_innacurate(since: float) -> bool: | ||
# Data older than 5 minutes is considered innacurate. | ||
return time.time() - since > 300 | ||
|
||
def _play_sound(self): | ||
try: | ||
Popen(['canberra-gtk-play', '-i', self.sound_id], stderr=open(os.devnull, 'wb')) | ||
except FileNotFoundError: | ||
pass | ||
|
||
def _send_notification(self, message): | ||
try: | ||
Popen(['notify-send', *self.notification_args, message['From'], message['Subject']], | ||
stderr=open(os.devnull, 'wb')) | ||
except FileNotFoundError: | ||
pass | ||
|
||
def run(self): | ||
session = {'history_id': None, 'unread': None} | ||
inaccurate = False | ||
if self.session_path.is_file(): | ||
with open(self.session_path, 'r') as f: | ||
session = json.loads(f.read()) | ||
inaccurate = self._is_innacurate(session['time']) | ||
self.printer.print(session['unread'], inaccurate=inaccurate) | ||
|
||
try: | ||
unread = self.gmail.get_unread_messages_count(self.label) | ||
if unread != session['unread'] or inaccurate == True: | ||
self.printer.print(unread) | ||
history_id = session['history_id'] or self.gmail.get_latest_history_id() | ||
session = { | ||
'history_id': history_id, | ||
'unread': unread, | ||
'time': time.time() | ||
} | ||
with open(self.session_path, 'w') as f: | ||
json.dump(session, f) | ||
|
||
if session['history_id']: | ||
history = self.gmail.get_history_since(session['history_id']) | ||
if any(history['messages']) and self.sound_id: | ||
self._play_sound() | ||
for message in history['messages']: | ||
print(message) | ||
self._send_notification(message) | ||
session['history_id'] = history['history_id'] | ||
with open(self.session_path, 'w') as f: | ||
json.dump(session, f) | ||
except HttpError as error: | ||
if error.resp.status == 404: | ||
self.printer.error(f'Label not found: {self.label}') | ||
except TransportError: | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{"installed":{"client_id":"435972834621-7lmn42355puhs92f7jifob4vd46mn73l.apps.googleusercontent.com","project_id":"bar-gmail","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"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","redirect_uris":["http://localhost"]}} |
Oops, something went wrong.