33import concurrent .futures
44import functools
55import json
6+ import re
67import os
78import requests
8- import subprocess
99import sys
10+ import platform
1011import traceback
1112import tempfile
1213import urwid
1314from datetime import datetime
14- from slackclient import SlackClient
15+
1516from sclack .components import Attachment , Channel , ChannelHeader , ChatBox , Dm
1617from sclack .components import Indicators , MarkdownText , Message , MessageBox
1718from sclack .components import NewMessagesDivider , Profile , ProfileSideBar
@@ -86,6 +87,48 @@ def __init__(self, config):
8687 unhandled_input = self .unhandled_input
8788 )
8889 self .configure_screen (self .urwid_loop .screen )
90+ self .mentioned_patterns = None
91+
92+ def get_mentioned_patterns (self ):
93+ slack_mentions = [
94+ '<!everyone>' ,
95+ '<!here>' ,
96+ '<!channel>' ,
97+ '<@{}>' .format (self .store .state .auth ['user_id' ]),
98+ ]
99+
100+ patterns = []
101+
102+ for mention in slack_mentions :
103+ patterns .append ('^{}[ ]+' .format (mention ))
104+ patterns .append ('^{}$' .format (mention ))
105+ patterns .append ('[ ]+{}' .format (mention ))
106+
107+ return re .compile ('|' .join (patterns ))
108+
109+ def should_notify_me (self , message_obj ):
110+ """
111+ Checking whether notify to user
112+ :param message_obj:
113+ :return:
114+ """
115+ # You send message, don't need notification
116+ if self .config ['features' ]['notification' ] in ['' , 'none' ] or message_obj ['user' ] == self .store .state .auth ['user_id' ]:
117+ return False
118+
119+ if self .config ['features' ]['notification' ] == 'all' :
120+ return True
121+
122+ # Private message
123+ if message_obj .get ('channel' ) is not None and message_obj .get ('channel' )[0 ] == 'D' :
124+ return True
125+
126+ regex = self .mentioned_patterns
127+ if regex is None :
128+ regex = self .get_mentioned_patterns ()
129+ self .mentioned_patterns = regex
130+
131+ return len (re .findall (regex , message_obj ['text' ])) > 0
89132
90133 def start (self ):
91134 self ._loading = True
@@ -140,6 +183,8 @@ def mount_sidebar(self, executor):
140183 loop .run_in_executor (executor , self .store .load_groups ),
141184 loop .run_in_executor (executor , self .store .load_users )
142185 )
186+ self .mentioned_patterns = self .get_mentioned_patterns ()
187+
143188 profile = Profile (name = self .store .state .auth ['user' ])
144189 channels = [
145190 Channel (
@@ -158,7 +203,7 @@ def mount_sidebar(self, executor):
158203 if user :
159204 dms .append (Dm (
160205 dm ['id' ],
161- name = user . get ( 'display_name' ) or user . get ( 'real_name' ) or user [ 'name' ] ,
206+ name = self . store . get_user_display_name ( user ) ,
162207 user = dm ['user' ],
163208 you = user ['id' ] == self .store .state .auth ['user_id' ]
164209 ))
@@ -244,7 +289,7 @@ def go_to_profile(self, user_id):
244289 return
245290 self .store .state .profile_user_id = user_id
246291 profile = ProfileSideBar (
247- user . get ( 'display_name' ) or user . get ( 'real_name' ) or user [ 'name' ] ,
292+ self . store . get_user_display_name ( user ) ,
248293 user ['profile' ].get ('status_text' , None ),
249294 user ['profile' ].get ('tz_label' , None ),
250295 user ['profile' ].get ('phone' , None ),
@@ -260,7 +305,7 @@ def render_chatbox_header(self):
260305 if self .store .state .channel ['id' ][0 ] == 'D' :
261306 user = self .store .find_user_by_id (self .store .state .channel ['user' ])
262307 header = ChannelHeader (
263- name = user . get ( 'display_name' ) or user . get ( 'real_name' ) or user [ 'name' ] ,
308+ name = self . store . get_user_display_name ( user ) ,
264309 topic = user ['profile' ]['status_text' ],
265310 is_starred = self .store .state .channel .get ('is_starred' , False ),
266311 is_dm_workaround_please_remove_me = True
@@ -282,6 +327,16 @@ def on_change_topic(self, text):
282327 self .store .set_topic (self .store .state .channel ['id' ], text )
283328 self .go_to_sidebar ()
284329
330+ def notification_messages (self , messages ):
331+ """
332+ Check and send notifications
333+ :param messages:
334+ :return:
335+ """
336+ for message in messages :
337+ if self .should_notify_me (message ):
338+ self .send_notification (message , MarkdownText (message ['text' ]))
339+
285340 def render_message (self , message ):
286341 is_app = False
287342 subtype = message .get ('subtype' )
@@ -331,6 +386,7 @@ def render_message(self, message):
331386 return None
332387
333388 user_id = user ['id' ]
389+ # TODO
334390 user_name = user ['profile' ]['display_name' ] or user .get ('name' )
335391 color = user .get ('color' )
336392 if message .get ('file' ):
@@ -343,6 +399,7 @@ def render_message(self, message):
343399 return None
344400
345401 user_id = user ['id' ]
402+ # TODO
346403 user_name = user ['profile' ]['display_name' ] or user .get ('name' )
347404 color = user .get ('color' )
348405
@@ -355,6 +412,7 @@ def render_message(self, message):
355412 ]
356413
357414 attachments = []
415+
358416 for attachment in message .get ('attachments' , []):
359417 attachment_widget = Attachment (
360418 service_name = attachment .get ('service_name' ),
@@ -428,8 +486,9 @@ def render_messages(self, messages):
428486 previous_date = self .store .state .last_date
429487 last_read_datetime = datetime .fromtimestamp (float (self .store .state .channel .get ('last_read' , '0' )))
430488 today = datetime .today ().date ()
431- for message in messages :
432- message_datetime = datetime .fromtimestamp (float (message ['ts' ]))
489+
490+ for raw_message in messages :
491+ message_datetime = datetime .fromtimestamp (float (raw_message ['ts' ]))
433492 message_date = message_datetime .date ()
434493 date_text = None
435494 unread_text = None
@@ -449,11 +508,50 @@ def render_messages(self, messages):
449508 _messages .append (NewMessagesDivider (unread_text , date = date_text ))
450509 elif date_text is not None :
451510 _messages .append (TextDivider (('history_date' , date_text ), 'center' ))
452- message = self .render_message (message )
511+
512+ message = self .render_message (raw_message )
513+
453514 if message is not None :
454515 _messages .append (message )
516+
455517 return _messages
456518
519+ def send_notification (self , raw_message , markdown_text ):
520+ """
521+ Only MacOS
522+ @TODO Linux libnotify and Windows
523+ :param raw_message:
524+ :param markdown_text:
525+ :return:
526+ """
527+ user = self .store .find_user_by_id (raw_message .get ('user' ))
528+ sender_name = self .store .get_user_display_name (user )
529+
530+ # TODO Checking bot
531+ if raw_message .get ('channel' )[0 ] == 'D' :
532+ notification_title = 'New message in {}' .format (
533+ self .store .state .auth ['team' ]
534+ )
535+ else :
536+ notification_title = 'New message in {} #{}' .format (
537+ self .store .state .auth ['team' ],
538+ self .store .get_channel_name (raw_message .get ('channel' )),
539+ )
540+
541+ sub_title = sender_name
542+
543+ if platform .system () == 'Darwin' :
544+ # Macos
545+ import pync
546+ pync .notify (
547+ markdown_text .render_text (),
548+ title = notification_title ,
549+ subtitle = sub_title ,
550+ appIcon = './resources/slack_icon.png'
551+ )
552+ else :
553+ pass
554+
457555 @asyncio .coroutine
458556 def _go_to_channel (self , channel_id ):
459557 with concurrent .futures .ThreadPoolExecutor (max_workers = 20 ) as executor :
@@ -533,6 +631,7 @@ def start_real_time(self):
533631 def stop_typing (* args ):
534632 self .chatbox .message_box .typing = None
535633 alarm = None
634+
536635 while self .store .slack .server .connected is True :
537636 events = self .store .slack .rtm_read ()
538637 for event in events :
@@ -543,9 +642,8 @@ def stop_typing(*args):
543642 for channel in self .sidebar .channels :
544643 if channel .id == event ['channel' ]:
545644 channel .set_unread (unread )
546- elif event .get ('channel' ) == self .store .state .channel ['id' ]:
547- if event ['type' ] == 'message' :
548- # Delete message
645+ elif event ['type' ] == 'message' :
646+ if event .get ('channel' ) == self .store .state .channel ['id' ]:
549647 if event .get ('subtype' ) == 'message_deleted' :
550648 for widget in self .chatbox .body .body :
551649 if hasattr (widget , 'ts' ) and getattr (widget , 'ts' ) == event ['deleted_ts' ]:
@@ -559,16 +657,23 @@ def stop_typing(*args):
559657 else :
560658 self .chatbox .body .body .extend (self .render_messages ([event ]))
561659 self .chatbox .body .scroll_to_bottom ()
562- elif event ['type' ] == 'user_typing' :
660+ else :
661+ pass
662+
663+ if event .get ('subtype' ) != 'message_deleted' and event .get ('subtype' ) != 'message_changed' :
664+ # Notification
665+ self .notification_messages ([event ])
666+ elif event ['type' ] == 'user_typing' :
667+ if event .get ('channel' ) == self .store .state .channel ['id' ]:
563668 user = self .store .find_user_by_id (event ['user' ])
564- name = user .get ('display_name' ) or user .get ('real_name' ) or user ['name' ]
669+ name = self .store .get_user_display_name (user )
670+
565671 if alarm is not None :
566672 self .urwid_loop .remove_alarm (alarm )
567673 self .chatbox .message_box .typing = name
568674 self .urwid_loop .set_alarm_in (3 , stop_typing )
569675 else :
570676 pass
571- # print(json.dumps(event, indent=2))
572677 elif event .get ('ok' , False ):
573678 # Message was sent, Slack confirmed it.
574679 self .chatbox .body .body .extend (self .render_messages ([{
0 commit comments