Skip to content

Commit 51e1c21

Browse files
Add notification when received message
1 parent 34319c7 commit 51e1c21

File tree

8 files changed

+152
-9
lines changed

8 files changed

+152
-9
lines changed

README.md

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,7 @@ Your `~/.sclack` file will look like:
7676

7777
### Multiple workspaces
7878

79-
If you want to, you can use Sclack in multiple workspaces. You can have
80-
at most 9 workspaces defined inside `workspaces`:
79+
If you want to, you can use Sclack in multiple workspaces. You can have at most 9 workspaces defined inside `workspaces`:
8180

8281
```json
8382
{
@@ -107,6 +106,26 @@ You can use <kbd>ctrl d</kbd> (or your custom shortcut) to set snooze time.
107106

108107
Focus on message and press <kbd>r</kbd> (or your custom shortcut) to get permalink (Quote message) and it will be put into your chat box.
109108

109+
110+
### Enable features
111+
112+
There are some features available, you can adjust them by change the config file
113+
114+
115+
```json
116+
{
117+
"features": {
118+
"emoji": true,
119+
"markdown": true,
120+
"pictures": true,
121+
"notification": ""
122+
},
123+
}
124+
```
125+
126+
* notification: How we send notification for you (*MacOS* supported now): `none` Disable notification / `mentioned` Direct message or mentioned in channel / `all` Receive all notifications
127+
128+
110129
### Default keybindings
111130
```json
112131
{
@@ -200,5 +219,7 @@ Contributions are very welcome, and there is a lot of work to do! You can...
200219
![](./resources/example_4.png)
201220
![](./resources/example_5.png)
202221
![](./resources/example_6.png)
222+
![](./resources/example_7.png)
223+
![](./resources/example_8.png)
203224

204225
<p align="center">Made with :rage: by <a href="https://github.com/haskellcamargo">@haskellcamargo</a></p>

TODO.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,4 @@
1818
- 'Do not disturb' status
1919
- Integration with reminders
2020
- Handle slash commands
21-
- RTM events (see https://api.slack.com/rtm)
21+
- RTM events (see https://api.slack.com/rtm)

app.py

Lines changed: 108 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@
33
import concurrent.futures
44
import functools
55
import json
6+
import re
67
import os
78
import requests
89
import sys
10+
import platform
911
import time
1012
import traceback
1113
import tempfile
1214
import urwid
1315
from datetime import datetime
16+
1417
from sclack.components import Attachment, Channel, ChannelHeader, ChatBox, Dm
1518
from sclack.components import Indicators, MarkdownText, MessageBox
1619
from sclack.component.message import Message
@@ -90,6 +93,48 @@ def __init__(self, config):
9093
)
9194
self.configure_screen(self.urwid_loop.screen)
9295
self.last_keypress = (0, None)
96+
self.mentioned_patterns = None
97+
98+
def get_mentioned_patterns(self):
99+
slack_mentions = [
100+
'<!everyone>',
101+
'<!here>',
102+
'<!channel>',
103+
'<@{}>'.format(self.store.state.auth['user_id']),
104+
]
105+
106+
patterns = []
107+
108+
for mention in slack_mentions:
109+
patterns.append('^{}[ ]+'.format(mention))
110+
patterns.append('^{}$'.format(mention))
111+
patterns.append('[ ]+{}'.format(mention))
112+
113+
return re.compile('|'.join(patterns))
114+
115+
def should_notify_me(self, message_obj):
116+
"""
117+
Checking whether notify to user
118+
:param message_obj:
119+
:return:
120+
"""
121+
# You send message, don't need notification
122+
if self.config['features']['notification'] in ['', 'none'] or message_obj['user'] == self.store.state.auth['user_id']:
123+
return False
124+
125+
if self.config['features']['notification'] == 'all':
126+
return True
127+
128+
# Private message
129+
if message_obj.get('channel') is not None and message_obj.get('channel')[0] == 'D':
130+
return True
131+
132+
regex = self.mentioned_patterns
133+
if regex is None:
134+
regex = self.get_mentioned_patterns()
135+
self.mentioned_patterns = regex
136+
137+
return len(re.findall(regex, message_obj['text'])) > 0
93138

94139
def start(self):
95140
self._loading = True
@@ -151,6 +196,8 @@ def mount_sidebar(self, executor):
151196
loop.run_in_executor(executor, self.store.load_users),
152197
loop.run_in_executor(executor, self.store.load_user_dnd),
153198
)
199+
self.mentioned_patterns = self.get_mentioned_patterns()
200+
154201
profile = Profile(name=self.store.state.auth['user'], is_snoozed=self.store.state.is_snoozed)
155202

156203
channels = []
@@ -345,7 +392,7 @@ def go_to_profile(self, user_id):
345392
return
346393
self.store.state.profile_user_id = user_id
347394
profile = ProfileSideBar(
348-
user.get('display_name') or user.get('real_name') or user['name'],
395+
self.store.get_user_display_name(user),
349396
user['profile'].get('status_text', None),
350397
user['profile'].get('tz_label', None),
351398
user['profile'].get('phone', None),
@@ -361,7 +408,7 @@ def render_chatbox_header(self):
361408
if self.store.state.channel['id'][0] == 'D':
362409
user = self.store.find_user_by_id(self.store.state.channel['user'])
363410
header = ChannelHeader(
364-
name=user.get('display_name') or user.get('real_name') or user['name'],
411+
name=self.store.get_user_display_name(user),
365412
topic=user['profile']['status_text'],
366413
is_starred=self.store.state.channel.get('is_starred', False),
367414
is_dm_workaround_please_remove_me=True
@@ -383,6 +430,16 @@ def on_change_topic(self, text):
383430
self.store.set_topic(self.store.state.channel['id'], text)
384431
self.go_to_sidebar()
385432

433+
def notification_messages(self, messages):
434+
"""
435+
Check and send notifications
436+
:param messages:
437+
:return:
438+
"""
439+
for message in messages:
440+
if self.should_notify_me(message):
441+
self.send_notification(message, MarkdownText(message['text']))
442+
386443
def render_message(self, message, channel_id=None):
387444
is_app = False
388445
subtype = message.get('subtype')
@@ -434,6 +491,7 @@ def render_message(self, message, channel_id=None):
434491
return None
435492

436493
user_id = user['id']
494+
# TODO
437495
user_name = user['profile']['display_name'] or user.get('name')
438496
color = user.get('color')
439497
if message.get('file'):
@@ -446,6 +504,7 @@ def render_message(self, message, channel_id=None):
446504
return None
447505

448506
user_id = user['id']
507+
# TODO
449508
user_name = user['profile']['display_name'] or user.get('name')
450509
color = user.get('color')
451510

@@ -458,6 +517,7 @@ def render_message(self, message, channel_id=None):
458517
]
459518

460519
attachments = []
520+
461521
for attachment in message.get('attachments', []):
462522
attachment_widget = Attachment(
463523
service_name=attachment.get('service_name'),
@@ -538,8 +598,9 @@ def render_messages(self, messages, channel_id=None):
538598
previous_date = self.store.state.last_date
539599
last_read_datetime = datetime.fromtimestamp(float(self.store.state.channel.get('last_read', '0')))
540600
today = datetime.today().date()
541-
for message in messages:
542-
message_datetime = datetime.fromtimestamp(float(message['ts']))
601+
602+
for raw_message in messages:
603+
message_datetime = datetime.fromtimestamp(float(raw_message['ts']))
543604
message_date = message_datetime.date()
544605
date_text = None
545606
unread_text = None
@@ -568,6 +629,42 @@ def render_messages(self, messages, channel_id=None):
568629

569630
return _messages
570631

632+
def send_notification(self, raw_message, markdown_text):
633+
"""
634+
Only MacOS
635+
@TODO Linux libnotify and Windows
636+
:param raw_message:
637+
:param markdown_text:
638+
:return:
639+
"""
640+
user = self.store.find_user_by_id(raw_message.get('user'))
641+
sender_name = self.store.get_user_display_name(user)
642+
643+
# TODO Checking bot
644+
if raw_message.get('channel')[0] == 'D':
645+
notification_title = 'New message in {}'.format(
646+
self.store.state.auth['team']
647+
)
648+
else:
649+
notification_title = 'New message in {} #{}'.format(
650+
self.store.state.auth['team'],
651+
self.store.get_channel_name(raw_message.get('channel')),
652+
)
653+
654+
sub_title = sender_name
655+
656+
if platform.system() == 'Darwin':
657+
# Macos
658+
import pync
659+
pync.notify(
660+
markdown_text.render_text(),
661+
title=notification_title,
662+
subtitle=sub_title,
663+
appIcon='./resources/slack_icon.png'
664+
)
665+
else:
666+
pass
667+
571668
def handle_mark_read(self, data):
572669
"""
573670
Mark as read to bottom
@@ -760,13 +857,18 @@ def stop_typing(*args):
760857
self.chatbox.body.scroll_to_bottom()
761858
else:
762859
pass
860+
861+
if event.get('subtype') != 'message_deleted' and event.get('subtype') != 'message_changed':
862+
# Notification
863+
self.notification_messages([event])
763864
elif event['type'] == 'user_typing':
764865
if not self.is_chatbox_rendered:
765866
return
766867

767868
if event.get('channel') == self.store.state.channel['id']:
768869
user = self.store.find_user_by_id(event['user'])
769-
name = user.get('display_name') or user.get('real_name') or user['name']
870+
name = self.store.get_user_display_name(user)
871+
770872
if alarm is not None:
771873
self.urwid_loop.remove_alarm(alarm)
772874
self.chatbox.message_box.typing = name
@@ -925,6 +1027,7 @@ def ask_for_token(json_config):
9251027
config_file.write(json.dumps(token_config, indent=False))
9261028
json_config.update(token_config)
9271029

1030+
9281031
if __name__ == '__main__':
9291032
json_config = {}
9301033
with open('./config.json', 'r') as config_file:

config.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@
2727
"emoji": true,
2828
"markdown": true,
2929
"pictures": true,
30-
"browser": ""
30+
"browser": "",
31+
"notification": ""
3132
},
3233
"icons": {
3334
"block": "\u258C",

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ pyperclip==1.6.2
44
requests
55
slackclient==1.2.1
66
urwid_readline
7+
git+git://github.com/duynguyenhoang/pync@994fbf77360a273fac1225558de01c8d0040dc6c#egg=pync

resources/slack_icon.png

57 KB
Loading

sclack/markdown.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ def parse_message(self, text):
4141
self._state = 'message'
4242
self._previous_state = 'message'
4343
self._result = []
44+
4445
def render_emoji(result):
4546
return emoji_codemap.get(result.group(1), result.group(0))
4647

@@ -72,3 +73,6 @@ def render_emoji(result):
7273

7374
self._result.append(('message', self.decode_buffer()))
7475
return self._result
76+
77+
def render_text(self):
78+
return urwid.Text(self.markup).text

sclack/store.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,19 @@ def load_channels(self):
162162
self.state.channels.sort(key=lambda channel: channel['name'])
163163
self.state.dms.sort(key=lambda dm: dm['created'])
164164

165+
def get_channel_name(self, channel_id):
166+
matched_channel = None
167+
168+
for channel in self.state.channels:
169+
if channel['id'] == channel_id:
170+
matched_channel = channel
171+
break
172+
173+
if matched_channel:
174+
return matched_channel['name']
175+
176+
return channel_id
177+
165178
def load_groups(self):
166179
self.state.groups = self.slack.api_call('mpim.list')['groups']
167180

0 commit comments

Comments
 (0)