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
@@ -80,6 +81,48 @@ def __init__(self, config):
8081 unhandled_input = self .unhandled_input
8182 )
8283 self .configure_screen (self .urwid_loop .screen )
84+ self .mentioned_patterns = None
85+
86+ def get_mentioned_patterns (self ):
87+ slack_mentions = [
88+ '<!everyone>' ,
89+ '<!here>' ,
90+ '<!channel>' ,
91+ '<@{}>' .format (self .store .state .auth ['user_id' ]),
92+ ]
93+
94+ patterns = []
95+
96+ for mention in slack_mentions :
97+ patterns .append ('^{}[ ]+' .format (mention ))
98+ patterns .append ('^{}$' .format (mention ))
99+ patterns .append ('[ ]+{}' .format (mention ))
100+
101+ return re .compile ('|' .join (patterns ))
102+
103+ def should_notify_me (self , message_obj ):
104+ """
105+ Checking whether notify to user
106+ :param message_obj:
107+ :return:
108+ """
109+ # You send message, don't need notification
110+ if self .config ['features' ]['notification' ] in ['' , 'none' ] or message_obj ['user' ] == self .store .state .auth ['user_id' ]:
111+ return False
112+
113+ if self .config ['features' ]['notification' ] == 'all' :
114+ return True
115+
116+ # Private message
117+ if message_obj .get ('channel' ) is not None and message_obj .get ('channel' )[0 ] == 'D' :
118+ return True
119+
120+ regex = self .mentioned_patterns
121+ if regex is None :
122+ regex = self .get_mentioned_patterns ()
123+ self .mentioned_patterns = regex
124+
125+ return len (re .findall (regex , message_obj ['text' ])) > 0
83126
84127 def start (self ):
85128 self ._loading = True
@@ -134,6 +177,8 @@ def mount_sidebar(self, executor):
134177 loop .run_in_executor (executor , self .store .load_groups ),
135178 loop .run_in_executor (executor , self .store .load_users )
136179 )
180+ self .mentioned_patterns = self .get_mentioned_patterns ()
181+
137182 profile = Profile (name = self .store .state .auth ['user' ])
138183 channels = [
139184 Channel (
@@ -152,7 +197,7 @@ def mount_sidebar(self, executor):
152197 if user :
153198 dms .append (Dm (
154199 dm ['id' ],
155- name = user . get ( 'display_name' ) or user . get ( 'real_name' ) or user [ 'name' ] ,
200+ name = self . store . get_user_display_name ( user ) ,
156201 user = dm ['user' ],
157202 you = user ['id' ] == self .store .state .auth ['user_id' ]
158203 ))
@@ -238,7 +283,7 @@ def go_to_profile(self, user_id):
238283 return
239284 self .store .state .profile_user_id = user_id
240285 profile = ProfileSideBar (
241- user . get ( 'display_name' ) or user . get ( 'real_name' ) or user [ 'name' ] ,
286+ self . store . get_user_display_name ( user ) ,
242287 user ['profile' ].get ('status_text' , None ),
243288 user ['profile' ].get ('tz_label' , None ),
244289 user ['profile' ].get ('phone' , None ),
@@ -254,7 +299,7 @@ def render_chatbox_header(self):
254299 if self .store .state .channel ['id' ][0 ] == 'D' :
255300 user = self .store .find_user_by_id (self .store .state .channel ['user' ])
256301 header = ChannelHeader (
257- name = user . get ( 'display_name' ) or user . get ( 'real_name' ) or user [ 'name' ] ,
302+ name = self . store . get_user_display_name ( user ) ,
258303 topic = user ['profile' ]['status_text' ],
259304 is_starred = self .store .state .channel .get ('is_starred' , False ),
260305 is_dm_workaround_please_remove_me = True
@@ -276,6 +321,16 @@ def on_change_topic(self, text):
276321 self .store .set_topic (self .store .state .channel ['id' ], text )
277322 self .go_to_sidebar ()
278323
324+ def notification_messages (self , messages ):
325+ """
326+ Check and send notifications
327+ :param messages:
328+ :return:
329+ """
330+ for message in messages :
331+ if self .should_notify_me (message ):
332+ self .send_notification (message , MarkdownText (message ['text' ]))
333+
279334 def render_message (self , message ):
280335 is_app = False
281336 subtype = message .get ('subtype' )
@@ -325,6 +380,7 @@ def render_message(self, message):
325380 return None
326381
327382 user_id = user ['id' ]
383+ # TODO
328384 user_name = user ['profile' ]['display_name' ] or user .get ('name' )
329385 color = user .get ('color' )
330386 if message .get ('file' ):
@@ -337,6 +393,7 @@ def render_message(self, message):
337393 return None
338394
339395 user_id = user ['id' ]
396+ # TODO
340397 user_name = user ['profile' ]['display_name' ] or user .get ('name' )
341398 color = user .get ('color' )
342399
@@ -349,6 +406,7 @@ def render_message(self, message):
349406 ]
350407
351408 attachments = []
409+
352410 for attachment in message .get ('attachments' , []):
353411 attachment_widget = Attachment (
354412 service_name = attachment .get ('service_name' ),
@@ -422,8 +480,9 @@ def render_messages(self, messages):
422480 previous_date = self .store .state .last_date
423481 last_read_datetime = datetime .fromtimestamp (float (self .store .state .channel .get ('last_read' , '0' )))
424482 today = datetime .today ().date ()
425- for message in messages :
426- message_datetime = datetime .fromtimestamp (float (message ['ts' ]))
483+
484+ for raw_message in messages :
485+ message_datetime = datetime .fromtimestamp (float (raw_message ['ts' ]))
427486 message_date = message_datetime .date ()
428487 date_text = None
429488 unread_text = None
@@ -443,11 +502,50 @@ def render_messages(self, messages):
443502 _messages .append (NewMessagesDivider (unread_text , date = date_text ))
444503 elif date_text is not None :
445504 _messages .append (TextDivider (('history_date' , date_text ), 'center' ))
446- message = self .render_message (message )
505+
506+ message = self .render_message (raw_message )
507+
447508 if message is not None :
448509 _messages .append (message )
510+
449511 return _messages
450512
513+ def send_notification (self , raw_message , markdown_text ):
514+ """
515+ Only MacOS
516+ @TODO Linux libnotify and Windows
517+ :param raw_message:
518+ :param markdown_text:
519+ :return:
520+ """
521+ user = self .store .find_user_by_id (raw_message .get ('user' ))
522+ sender_name = self .store .get_user_display_name (user )
523+
524+ # TODO Checking bot
525+ if raw_message .get ('channel' )[0 ] == 'D' :
526+ notification_title = 'New message in {}' .format (
527+ self .store .state .auth ['team' ]
528+ )
529+ else :
530+ notification_title = 'New message in {} #{}' .format (
531+ self .store .state .auth ['team' ],
532+ self .store .get_channel_name (raw_message .get ('channel' )),
533+ )
534+
535+ sub_title = sender_name
536+
537+ if platform .system () == 'Darwin' :
538+ # Macos
539+ import pync
540+ pync .notify (
541+ markdown_text .render_text (),
542+ title = notification_title ,
543+ subtitle = sub_title ,
544+ appIcon = './resources/slack_icon.png'
545+ )
546+ else :
547+ pass
548+
451549 @asyncio .coroutine
452550 def _go_to_channel (self , channel_id ):
453551 with concurrent .futures .ThreadPoolExecutor (max_workers = 20 ) as executor :
@@ -527,6 +625,7 @@ def start_real_time(self):
527625 def stop_typing (* args ):
528626 self .chatbox .message_box .typing = None
529627 alarm = None
628+
530629 while self .store .slack .server .connected is True :
531630 events = self .store .slack .rtm_read ()
532631 for event in events :
@@ -537,9 +636,8 @@ def stop_typing(*args):
537636 for channel in self .sidebar .channels :
538637 if channel .id == event ['channel' ]:
539638 channel .set_unread (unread )
540- elif event .get ('channel' ) == self .store .state .channel ['id' ]:
541- if event ['type' ] == 'message' :
542- # Delete message
639+ elif event ['type' ] == 'message' :
640+ if event .get ('channel' ) == self .store .state .channel ['id' ]:
543641 if event .get ('subtype' ) == 'message_deleted' :
544642 for widget in self .chatbox .body .body :
545643 if hasattr (widget , 'ts' ) and getattr (widget , 'ts' ) == event ['deleted_ts' ]:
@@ -553,16 +651,23 @@ def stop_typing(*args):
553651 else :
554652 self .chatbox .body .body .extend (self .render_messages ([event ]))
555653 self .chatbox .body .scroll_to_bottom ()
556- elif event ['type' ] == 'user_typing' :
654+ else :
655+ pass
656+
657+ if event .get ('subtype' ) != 'message_deleted' and event .get ('subtype' ) != 'message_changed' :
658+ # Notification
659+ self .notification_messages ([event ])
660+ elif event ['type' ] == 'user_typing' :
661+ if event .get ('channel' ) == self .store .state .channel ['id' ]:
557662 user = self .store .find_user_by_id (event ['user' ])
558- name = user .get ('display_name' ) or user .get ('real_name' ) or user ['name' ]
663+ name = self .store .get_user_display_name (user )
664+
559665 if alarm is not None :
560666 self .urwid_loop .remove_alarm (alarm )
561667 self .chatbox .message_box .typing = name
562668 self .urwid_loop .set_alarm_in (3 , stop_typing )
563669 else :
564670 pass
565- # print(json.dumps(event, indent=2))
566671 elif event .get ('ok' , False ):
567672 # Message was sent, Slack confirmed it.
568673 self .chatbox .body .body .extend (self .render_messages ([{
0 commit comments