diff --git a/app.py b/app.py index 28c0493..1c867c3 100755 --- a/app.py +++ b/app.py @@ -23,6 +23,7 @@ from sclack.themes import themes from sclack.widgets.set_snooze import SetSnoozeWidget +from sclack.utils.channel import is_dm, is_group, is_channel loop = asyncio.get_event_loop() @@ -157,26 +158,27 @@ def mount_sidebar(self, executor): # Prepare list of Star users and channels for dm in self.store.state.stars: - # Group chat is not supported, prefer to https://github.com/haskellcamargo/sclack/issues/67 - if self.store.is_dm(dm['channel']): + if is_dm(dm['channel']): detail = self.store.get_channel_info(dm['channel']) user = self.store.find_user_by_id(detail['user']) + if user: stars_user_id.append(user['id']) star_user_tmp.append(Dm( dm['channel'], name=self.store.get_user_display_name(user), - user=dm['channel'], + user=user['id'], you=False )) - elif self.store.is_channel(dm['channel']): + elif is_channel(dm['channel']) or is_group(dm['channel']): channel = self.store.get_channel_info(dm['channel']) - if channel: + # Group chat (is_mpim) is not supported, prefer to https://github.com/haskellcamargo/sclack/issues/67 + if channel and not channel.get('is_archived', False) and not channel.get('is_mpim', False): stars_channel_id.append(channel['id']) stars.append(Channel( id=channel['id'], name=channel['name'], - is_private=channel['is_private'] + is_private=channel.get('is_private', True) )) stars.extend(star_user_tmp) @@ -206,9 +208,9 @@ def mount_sidebar(self, executor): self.sidebar = SideBar(profile, channels, dms, stars=stars, title=self.store.state.auth['team']) urwid.connect_signal(self.sidebar, 'go_to_channel', self.go_to_channel) - loop.create_task(self.get_channels_info(executor, channels)) - loop.create_task(self.get_presences(executor, dms)) - loop.create_task(self.get_dms_unread(executor, dms)) + loop.create_task(self.get_channels_info(executor, self.sidebar.get_all_channels())) + loop.create_task(self.get_presences(executor, self.sidebar.get_all_dms())) + loop.create_task(self.get_dms_unread(executor, self.sidebar.get_all_dms())) @asyncio.coroutine def get_presences(self, executor, dm_widgets): @@ -693,16 +695,27 @@ def stop_typing(*args): self.chatbox.message_box.typing = None alarm = None + while self.store.slack.server.connected is True: events = self.store.slack.rtm_read() + for event in events: if event.get('type') == 'hello': pass - elif event.get('type') in ('channel_marked', 'group_marked'): + elif event.get('type') in ('channel_marked', 'group_marked', 'im_marked'): unread = event.get('unread_count_display', 0) - for channel in self.sidebar.channels: - if channel.id == event['channel']: - channel.set_unread(unread) + + if event.get('type') == 'channel_marked': + targets = self.sidebar.get_all_channels() + elif event.get('type') == 'group_marked': + targets = self.sidebar.get_all_groups() + else: + targets = self.sidebar.get_all_dms() + + for target in targets: + if target.id == event['channel']: + target.set_unread(unread) + elif event['type'] == 'message': loop.create_task( self.update_chat(event) diff --git a/sclack/components.py b/sclack/components.py index 3eac819..9abc37f 100644 --- a/sclack/components.py +++ b/sclack/components.py @@ -10,6 +10,7 @@ from .emoji import emoji_codemap from .markdown import MarkdownText from .store import Store +from sclack.utils.channel import is_group, is_channel, is_dm MARK_READ_ALARM_PERIOD = 3 @@ -370,7 +371,7 @@ def handle_floating_date(self, size): class Dm(urwid.AttrMap): - def __init__(self, id, name, user, you=False, unread=0): + def __init__(self, id, name, user, you=False, unread=0, is_selected=False): self.id = id self.user = user self.name = name @@ -378,8 +379,13 @@ def __init__(self, id, name, user, you=False, unread=0): self.presence = 'away' self.unread = unread self.body = urwid.SelectableIcon(self.get_markup()) - self.is_selected = False - super(Dm, self).__init__(self.body, 'inactive', 'active_channel') + self.is_selected = is_selected + + attr_map = 'inactive' + if is_selected: + attr_map = 'selected_channel' + + super(Dm, self).__init__(self.body, attr_map, 'active_channel') def get_markup(self, presence='away'): if self.user == 'USLACKBOT': @@ -417,11 +423,11 @@ def get_markup(self, presence='away'): def set_unread(self, count): self.unread = count - - if count > 0: - self.attr_map = {None: 'unread_channel'} - else: - self.attr_map = {None: 'inactive'} + if not self.is_selected: + if count > 0: + self.attr_map = {None: 'unread_channel'} + else: + self.attr_map = {None: 'inactive'} self.body.set_text(self.get_markup(self.presence)) @@ -440,12 +446,15 @@ def select(self): 'presence_away': 'selected_channel' } self.set_presence(self.presence) + self.attr_map = {None: 'selected_channel'} + self.focus_map = {None: 'selected_channel'} def deselect(self): self.is_selected = False self.attr_map = {None: None} self.set_presence(self.presence) + class Fields(urwid.Pile): def chunks(self, list, size): for i in range(0, len(list), size): @@ -650,6 +659,7 @@ def text(self, text): self.prompt_widget.set_edit_text(text) self.prompt_widget.set_edit_pos(len(text)) + class MessagePrompt(urwid_readline.ReadlineEdit): __metaclass__ = urwid.MetaSignals signals = ['submit_message', 'go_to_last_message'] @@ -748,6 +758,7 @@ def __init__(self, profile, channels=(), dms=(), stars=(), title=''): self.channels = channels self.stars = stars self.dms = dms + self.groups = () # Subscribe to receive message from channels to select them for channel in self.channels: @@ -768,13 +779,72 @@ def __init__(self, profile, channels=(), dms=(), stars=(), title=''): self.listbox = urwid.ListBox(self.walker) super(SideBar, self).__init__(self.listbox, header=header, footer=footer) + def get_all_channels(self): + """ + List Channels including Starred items + :return: + """ + channels_starred = list(filter( + lambda starred: is_channel(starred.id), + self.stars + )) + channels_starred.extend(self.channels) + + return channels_starred + + def get_all_dms(self): + """ + List DM including Starred items + :return: + """ + dms = list(filter( + lambda starred: is_dm(starred.id), + self.stars + )) + dms.extend(self.dms) + + return dms + + def get_all_groups(self): + """ + List Groups including Starred items + :return: + """ + groups = list(filter( + lambda starred: is_group(starred.id), + self.stars + )) + groups.extend(self.groups) + + return groups + + def get_targets_by_id(self, channel_id): + """ + For different channel_id we get different data from: Groups, DMs, Channels + :param channel_id: + :return: + """ + targets = None + if is_channel(channel_id): + targets = self.get_all_channels() + elif is_dm(channel_id): + targets = self.get_all_dms() + elif is_group(channel_id): + targets = self.get_all_groups() + return targets + def select_channel(self, channel_id): - for channel in self.channels: + """ + :param channel_id: + :return: + """ + for channel in self.get_all_channels(): if channel.id == channel_id: channel.select() else: channel.deselect() - for dm in self.dms: + + for dm in self.get_all_dms(): if dm.id == channel_id: dm.select() else: @@ -787,11 +857,7 @@ def update_items(self, event): :return: """ channel_id = event.get('channel') - - if channel_id[0] == 'D': - target = self.dms - else: - target = self.channels + target = self.get_targets_by_id(channel_id) chat_detail = Store.instance.get_channel_info(event.get('channel')) new_count = chat_detail.get('unread_count_display', 0) diff --git a/sclack/store.py b/sclack/store.py index 8e55eda..77f688a 100644 --- a/sclack/store.py +++ b/sclack/store.py @@ -20,11 +20,13 @@ def __init__(self): self.online_users = set() self.is_snoozed = False + class Cache: def __init__(self): self.avatar = {} self.picture = {} + class Store: def __init__(self, workspaces, config): self.workspaces = workspaces @@ -165,7 +167,7 @@ def load_stars(self): :return: """ self.state.stars = list(filter( - lambda star: star.get('type', '') in ('channel', 'im', 'group', ), + lambda star: star.get('type', '') in ('channel', 'im', 'group',), self.slack.api_call('stars.list')['items'] )) diff --git a/sclack/utils/channel.py b/sclack/utils/channel.py new file mode 100644 index 0000000..02d280a --- /dev/null +++ b/sclack/utils/channel.py @@ -0,0 +1,48 @@ +def get_group_name(group_raw_name): + """ + TODO Remove last number + :param group_raw_name: + :return: + """ + if group_raw_name[:5] == 'mpdm-': + group_parts = group_raw_name[5:].split('--') + group_parts = ['@{}'.format(item) for item in group_parts] + return ', '.join(group_parts) + + return group_raw_name + + +def is_valid_channel_id(channel_id): + """ + Check whether channel_id is valid + :param channel_id: + :return: + """ + return channel_id[0] in ('C', 'G', 'D') + + +def is_channel(channel_id): + """ + Is a channel + :param channel_id: + :return: + """ + return channel_id[0] == 'C' + + +def is_dm(channel_id): + """ + Is direct message + :param channel_id: + :return: + """ + return channel_id[0] == 'D' + + +def is_group(channel_id): + """ + Is a group + :param channel_id: + :return: + """ + return channel_id[0] == 'G'