aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--_pytest/data/http/rtm.start.json8
-rw-r--r--wee_slack.py187
2 files changed, 152 insertions, 43 deletions
diff --git a/_pytest/data/http/rtm.start.json b/_pytest/data/http/rtm.start.json
index d9cc464..34d875a 100644
--- a/_pytest/data/http/rtm.start.json
+++ b/_pytest/data/http/rtm.start.json
@@ -307,6 +307,8 @@
"creator": "U407ABLLW",
"is_archived": false,
"is_general": true,
+ "is_shared": false,
+ "is_org_shared": false,
"has_pins": false,
"is_member": true,
"last_read": "1485976236.000019",
@@ -392,6 +394,8 @@
"creator": "U407ABLLW",
"is_archived": false,
"is_general": false,
+ "is_shared": false,
+ "is_org_shared": false,
"has_pins": false,
"is_member": false,
"previous_names": []
@@ -404,6 +408,8 @@
"creator": "U407ABLLW",
"is_archived": false,
"is_general": false,
+ "is_shared": false,
+ "is_org_shared": false,
"has_pins": false,
"is_member": true,
"last_read": "1485969592.000002",
@@ -441,6 +447,8 @@
"creator": "U407ABLLW",
"is_archived": false,
"is_general": false,
+ "is_shared": false,
+ "is_org_shared": false,
"has_pins": false,
"is_member": false,
"previous_names": [
diff --git a/wee_slack.py b/wee_slack.py
index c0eda21..f14cfa8 100644
--- a/wee_slack.py
+++ b/wee_slack.py
@@ -72,6 +72,13 @@ SLACK_API_TRANSLATOR = {
"mark": "groups.mark",
"info": "groups.info"
},
+ "shared": {
+ "history": "conversations.history",
+ "join": "conversations.join",
+ "leave": "conversations.leave",
+ "mark": "channels.mark",
+ "info": "conversations.info",
+ },
"thread": {
"history": None,
"join": None,
@@ -122,6 +129,7 @@ def utf8_decode(f):
NICK_GROUP_HERE = "0|Here"
NICK_GROUP_AWAY = "1|Away"
+NICK_GROUP_EXTERNAL = "2|External"
sslopt_ca_certs = {}
if hasattr(ssl, "get_default_verify_paths") and callable(ssl.get_default_verify_paths):
@@ -998,7 +1006,8 @@ class SlackTeam(object):
Team object under which users and channels live.. Does lots.
"""
- def __init__(self, eventrouter, token, websocket_url, subdomain, nick, myidentifier, users, bots, channels, **kwargs):
+ def __init__(self, eventrouter, token, websocket_url, team_info, nick, myidentifier, users, bots, channels, **kwargs):
+ self.identifier = team_info["id"]
self.ws_url = websocket_url
self.connected = False
self.connecting = False
@@ -1008,8 +1017,8 @@ class SlackTeam(object):
self.eventrouter = eventrouter
self.token = token
self.team = self
- self.subdomain = subdomain
- self.domain = subdomain + ".slack.com"
+ self.subdomain = team_info["domain"]
+ self.domain = self.subdomain + ".slack.com"
self.preferred_name = self.domain
self.nick = nick
self.myidentifier = myidentifier
@@ -1129,7 +1138,7 @@ class SlackTeam(object):
def is_user_present(self, user_id):
user = self.users.get(user_id)
- if user.presence == 'active':
+ if user and user.presence == 'active':
return True
else:
return False
@@ -1295,6 +1304,8 @@ class SlackChannel(object):
prepend = ">"
elif self.type == "group":
prepend = config.group_name_prefix
+ elif self.type == "shared":
+ prepend = config.shared_name_prefix
else:
prepend = "#"
select = {
@@ -1600,7 +1611,7 @@ class SlackChannel(object):
def update_nicklist(self, user=None):
if not self.channel_buffer:
return
- if self.type not in ["channel", "group", "mpim"]:
+ if self.type not in ["channel", "group", "mpim", "shared"]:
return
w.buffer_set(self.channel_buffer, "nicklist", "1")
# create nicklists for the current channel if they don't exist
@@ -1612,16 +1623,25 @@ class SlackChannel(object):
if not afk:
afk = w.nicklist_add_group(self.channel_buffer, '', NICK_GROUP_AWAY, "weechat.color.nicklist_group", 1)
+ # Add External nicklist group only for shared channels
+ if self.type == 'shared':
+ external = w.nicklist_search_group(self.channel_buffer, '', NICK_GROUP_EXTERNAL)
+ if not external:
+ external = w.nicklist_add_group(self.channel_buffer, '', NICK_GROUP_EXTERNAL, 'weechat.color.nicklist_group', 2)
+
if user and len(self.members) < 1000:
- user = self.team.users[user]
- if user.deleted:
+ user = self.team.users.get(user)
+ # External users that have left shared channels won't exist
+ if not user or user.deleted:
return
nick = w.nicklist_search_nick(self.channel_buffer, "", user.name)
# since this is a change just remove it regardless of where it is
w.nicklist_remove_nick(self.channel_buffer, nick)
# now add it back in to whichever..
nick_group = afk
- if self.team.is_user_present(user.identifier):
+ if user.is_external:
+ nick_group = external
+ elif self.team.is_user_present(user.identifier):
nick_group = here
if user.identifier in self.members:
w.nicklist_add_nick(self.channel_buffer, nick_group, user.name, user.color_name, "", "", 1)
@@ -1631,11 +1651,13 @@ class SlackChannel(object):
if len(self.members) < 1000:
try:
for user in self.members:
- user = self.team.users[user]
+ user = self.team.users.get(user)
if user.deleted:
continue
nick_group = afk
- if self.team.is_user_present(user.identifier):
+ if user.is_external:
+ nick_group = external
+ elif self.team.is_user_present(user.identifier):
nick_group = here
w.nicklist_add_nick(self.channel_buffer, nick_group, user.name, user.color_name, "", "", 1)
except Exception as e:
@@ -1681,12 +1703,19 @@ class SlackDMChannel(SlackChannel):
def __init__(self, eventrouter, users, **kwargs):
dmuser = kwargs["user"]
- kwargs["name"] = users[dmuser].name
+ kwargs["name"] = users[dmuser].name if dmuser in users else dmuser
super(SlackDMChannel, self).__init__(eventrouter, **kwargs)
self.type = 'im'
self.update_color()
self.set_name(self.slack_name)
- self.topic = create_user_status_string(users[dmuser].profile)
+ if dmuser in users:
+ self.topic = create_user_status_string(users[dmuser].profile)
+
+ def set_related_server(self, team):
+ super(SlackDMChannel, self).set_related_server(team)
+ if self.user not in self.team.users:
+ s = SlackRequest(self.team.token, 'users.info', {'user': self.slack_name}, team_hash=self.team.team_hash, channel_identifier=self.identifier)
+ self.eventrouter.receive(s)
def set_name(self, slack_name):
self.name = slack_name
@@ -1818,6 +1847,29 @@ class SlackMPDMChannel(SlackChannel):
pass
+class SlackSharedChannel(SlackChannel):
+ def __init__(self, eventrouter, **kwargs):
+ super(SlackSharedChannel, self).__init__(eventrouter, **kwargs)
+ self.type = 'shared'
+
+ def set_related_server(self, team):
+ super(SlackSharedChannel, self).set_related_server(team)
+ # Fetch members here (after the team is known) since they aren't
+ # included in rtm.start
+ s = SlackRequest(team.token, 'conversations.members', {'channel': self.identifier}, team_hash=team.team_hash, channel_identifier=self.identifier)
+ self.eventrouter.receive(s)
+
+ def get_history(self, slow_queue=False):
+ # Get info for external users in the channel
+ for user in self.members - set(self.team.users.keys()):
+ s = SlackRequest(self.team.token, 'users.info', {'user': user}, team_hash=self.team.team_hash, channel_identifier=self.identifier)
+ self.eventrouter.receive(s)
+ super(SlackSharedChannel, self).get_history(slow_queue)
+
+ def set_name(self, slack_name):
+ self.name = config.shared_name_prefix + slack_name
+
+
class SlackThreadChannel(object):
"""
A thread channel is a virtual channel. We don't inherit from
@@ -1968,11 +2020,15 @@ class SlackUser(object):
Represends an individual slack user. Also where you set their name formatting.
"""
- def __init__(self, **kwargs):
- # We require these two things for a valid object,
- # the rest we can just learn from slack
+ def __init__(self, originating_team_id, **kwargs):
self.identifier = kwargs["id"]
- self.profile = {} # in case it's not in kwargs
+ # These attributes may be missing in the response, so we have to make
+ # sure they're set
+ self.profile = {}
+ self.presence = kwargs.get("presence", "unknown")
+ self.deleted = kwargs.get("deleted", False)
+ self.is_external = (not kwargs.get("is_bot") and
+ kwargs.get("team_id") != originating_team_id)
for key, value in kwargs.items():
setattr(self, key, value)
@@ -2014,8 +2070,8 @@ class SlackBot(SlackUser):
Basically the same as a user, but split out to identify and for future
needs
"""
- def __init__(self, **kwargs):
- super(SlackBot, self).__init__(**kwargs)
+ def __init__(self, originating_team_id, **kwargs):
+ super(SlackBot, self).__init__(originating_team_id, is_bot=True, **kwargs)
class SlackMessage(object):
@@ -2048,6 +2104,15 @@ class SlackMessage(object):
if message_json.get('subtype') == 'me_message' and not message_json['text'].startswith(self.sender):
message_json['text'] = self.sender + ' ' + self.message_json['text']
+ # Replace Slack link with direct link to shared file (required if
+ # shared by an external user since only the direct link is accessible)
+ if message_json.get('subtype') == 'file_share':
+ message_json['text'] = re.sub(
+ r'<http.+\|',
+ r'<{}|'.format(message_json['file']['url_private']),
+ message_json['text']
+ )
+
def __hash__(self):
return hash(self.ts)
@@ -2067,30 +2132,27 @@ class SlackMessage(object):
def get_sender(self):
name = ""
name_plain = ""
- if 'user' in self.message_json:
- if self.message_json['user'] == self.team.myidentifier:
- u = self.team.users[self.team.myidentifier]
- elif self.message_json['user'] in self.team.users:
- u = self.team.users[self.message_json['user']]
- name = "{}".format(u.formatted_name())
- name_plain = "{}".format(u.formatted_name(enable_color=False))
+ user = self.team.users.get(self.message_json.get('user'))
+ if user:
+ name = "{}".format(user.formatted_name())
+ name_plain = "{}".format(user.formatted_name(enable_color=False))
+ if user.is_external:
+ name += config.external_user_suffix
+ name_plain += config.external_user_suffix
elif 'username' in self.message_json:
- u = self.message_json["username"]
+ username = self.message_json["username"]
if self.message_json.get("subtype") == "bot_message":
- name = "{} :]".format(u)
- name_plain = "{}".format(u)
+ name = "{} :]".format(username)
+ name_plain = "{}".format(username)
else:
- name = "-{}-".format(u)
- name_plain = "{}".format(u)
+ name = "-{}-".format(username)
+ name_plain = "{}".format(username)
elif 'service_name' in self.message_json:
name = "-{}-".format(self.message_json["service_name"])
name_plain = "{}".format(self.message_json["service_name"])
elif self.message_json.get('bot_id') in self.team.bots:
name = "{} :]".format(self.team.bots[self.message_json["bot_id"]].formatted_name())
name_plain = "{}".format(self.team.bots[self.message_json["bot_id"]].formatted_name(enable_color=False))
- else:
- name = ""
- name_plain = ""
return (name, name_plain)
def add_reaction(self, reaction, user):
@@ -2205,15 +2267,18 @@ def handle_rtmstart(login_data, eventrouter):
users = {}
for item in login_data["users"]:
- users[item["id"]] = SlackUser(**item)
+ users[item["id"]] = SlackUser(login_data['team']['id'], **item)
bots = {}
for item in login_data["bots"]:
- bots[item["id"]] = SlackBot(**item)
+ bots[item["id"]] = SlackBot(login_data['team']['id'], **item)
channels = {}
for item in login_data["channels"]:
- channels[item["id"]] = SlackChannel(eventrouter, **item)
+ if item["is_shared"]:
+ channels[item["id"]] = SlackSharedChannel(eventrouter, **item)
+ else:
+ channels[item["id"]] = SlackChannel(eventrouter, **item)
for item in login_data["ims"]:
channels[item["id"]] = SlackDMChannel(eventrouter, users, **item)
@@ -2228,7 +2293,7 @@ def handle_rtmstart(login_data, eventrouter):
eventrouter,
metadata.token,
login_data['url'],
- login_data["team"]["domain"],
+ login_data["team"],
login_data["self"]["name"],
login_data["self"]["id"],
users,
@@ -2310,6 +2375,10 @@ def handle_mpimhistory(message_json, eventrouter, **kwargs):
handle_history(message_json, eventrouter, **kwargs)
+def handle_conversationshistory(message_json, eventrouter, **kwargs):
+ handle_history(message_json, eventrouter, **kwargs)
+
+
def handle_history(message_json, eventrouter, **kwargs):
request_metadata = pickle.loads(message_json["wee_slack_request_metadata"])
kwargs['team'] = eventrouter.teams[request_metadata.team_hash]
@@ -2326,6 +2395,29 @@ def handle_history(message_json, eventrouter, **kwargs):
process_message(message, eventrouter, **kwargs)
+def handle_conversationsmembers(members_json, eventrouter, **kwargs):
+ request_metadata = pickle.loads(members_json['wee_slack_request_metadata'])
+ team = eventrouter.teams[request_metadata.team_hash]
+ channel = team.channels[request_metadata.channel_identifier]
+ channel.members = set(members_json['members'])
+
+
+def handle_usersinfo(user_json, eventrouter, **kwargs):
+ request_metadata = pickle.loads(user_json['wee_slack_request_metadata'])
+ team = eventrouter.teams[request_metadata.team_hash]
+ channel = team.channels[request_metadata.channel_identifier]
+ user_info = user_json['user']
+ user = SlackUser(team.identifier, **user_info)
+ team.users[user_info['id']] = user
+
+ if channel.type == 'shared':
+ channel.update_nicklist(user_info['id'])
+ elif channel.type == 'im':
+ channel.slack_name = user.name
+ channel.set_topic(create_user_status_string(user.profile))
+
+
+
###### New/converted process_ and subprocess_ methods
def process_hello(message_json, eventrouter, **kwargs):
kwargs['team'].subscribe_users_presence()
@@ -2386,7 +2478,7 @@ def process_user_typing(message_json, eventrouter, **kwargs):
def process_team_join(message_json, eventrouter, **kwargs):
user = message_json['user']
team = kwargs["team"]
- team.users[user["id"]] = SlackUser(**user)
+ team.users[user["id"]] = SlackUser(team.identifier, **user)
def process_pong(message_json, eventrouter, **kwargs):
@@ -2889,11 +2981,12 @@ def resolve_ref(ref):
e = EVENTROUTER
if ref.startswith('@U') or ref.startswith('@W'):
for t in e.teams.keys():
- if ref[1:] in e.teams[t].users:
- # try:
- return "@{}".format(e.teams[t].users[ref[1:]].name)
- # except:
- # dbg("NAME: {}".format(ref))
+ user = e.teams[t].users.get(ref[1:])
+ if user:
+ name = '@{}'.format(user.name)
+ if user.is_external:
+ name += config.external_user_suffix
+ return name
elif ref.startswith('#C'):
for t in e.teams.keys():
if ref[1:] in e.teams[t].channels:
@@ -3732,6 +3825,9 @@ class PluginConfig(object):
'distracting_channels': Setting(
default='',
desc='List of channels to hide.'),
+ 'external_user_suffix': Setting(
+ default='*',
+ desc='The suffix appended to nicks to indicate external users.'),
'group_name_prefix': Setting(
default='&',
desc='The prefix of buffer names for groups (private channels).'),
@@ -3763,6 +3859,9 @@ class PluginConfig(object):
' will be used instead of the actual name of the slack (in buffer'
' names, logging, etc). E.g `work:no_fun_allowed` would make your'
' work slack show up as `no_fun_allowed` rather than `work.slack.com`.'),
+ 'shared_name_prefix': Setting(
+ default='%',
+ desc='The prefix of buffer names for shared channels.'),
'short_buffer_names': Setting(
default='false',
desc='Use `foo.#channel` rather than `foo.slack.com.#channel` as the'
@@ -3862,10 +3961,12 @@ class PluginConfig(object):
return w.config_get_plugin(key) == default
get_debug_level = get_int
+ get_external_user_suffix = get_string
get_group_name_prefix = get_string
get_map_underline_to = get_string
get_render_bold_as = get_string
get_render_italic_as = get_string
+ get_shared_name_prefix = get_string
get_slack_timeout = get_int
get_thread_suffix_color = get_string
get_unfurl_auto_link_display = get_string