aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md23
-rw-r--r--_pytest/conftest.py4
-rw-r--r--_pytest/test_process_message.py5
-rw-r--r--_pytest/test_unfurl.py42
-rw-r--r--wee_slack.py211
5 files changed, 202 insertions, 83 deletions
diff --git a/README.md b/README.md
index fb82724..67106d4 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,8 @@ A WeeChat native client for Slack.com. Provides supplemental features only avail
Features
--------
- * **New** Emoji reactions!
+ * **New** Upload to slack capabilities!
+ * Emoji reactions!
* Edited messages work just like the official clients, where the original message changes and has (edited) appended.
* Unfurled urls dont generate a new message, but replace the original with more info as it is received.
* Regex style message editing (s/oldtext/newtext/)
@@ -45,7 +46,6 @@ Dependencies
------------
* WeeChat 1.1+ http://weechat.org/
* websocket-client https://pypi.python.org/pypi/websocket-client/
- * curl http://curl.haxx.se/
Setup
------
@@ -65,19 +65,14 @@ wee-slack doesn't use the Slack IRC gateway. If you currently connect via the ga
####1. Install dependencies
-##### OSX
+##### OSX and Linux
```
pip install websocket-client
```
-##### Linux (ubuntu)
-```
-sudo apt-get install curl
-pip install websocket-client
-```
##### FreeBSD
```
-pkg install curl py27-websocket-client py27-six
+pkg install py27-websocket-client py27-six
```
####2. copy wee_slack.py to ~/.weechat/python/autoload
@@ -170,11 +165,21 @@ Turn off colorized nicks:
/set plugins.var.python.slack_extension.colorize_nicks 0
```
+Set channel prefix to something other than my-slack-subdomain.slack.com (e.g. when using buffers.pl):
+```
+/set plugins.var.python.slack_extension.server_alias.my-slack-subdomain "mysub"
+```
+
Set all read markers to a specific time:
```
/slack setallreadmarkers (time in epoch)
```
+Upload a file to the current slack buffer:
+```
+/slack upload [file_path]
+```
+
Debug mode:
```
/slack debug
diff --git a/_pytest/conftest.py b/_pytest/conftest.py
index 0cbceb9..2f90977 100644
--- a/_pytest/conftest.py
+++ b/_pytest/conftest.py
@@ -1,4 +1,8 @@
import pytest
+import sys
+
+sys.path.append(str(pytest.config.rootdir))
+
from wee_slack import SlackServer
from wee_slack import Channel
from wee_slack import User
diff --git a/_pytest/test_process_message.py b/_pytest/test_process_message.py
index 6c58336..a8cf9a2 100644
--- a/_pytest/test_process_message.py
+++ b/_pytest/test_process_message.py
@@ -33,8 +33,3 @@ def test_process_message(slack_debug, monkeypatch, myservers, mychannels, myuser
print called
# assert called['buffer_prnt'] == 2
# assert called['buffer_prnt_changed'] == 1
-
-
-
-
-
diff --git a/_pytest/test_unfurl.py b/_pytest/test_unfurl.py
new file mode 100644
index 0000000..9af7cf2
--- /dev/null
+++ b/_pytest/test_unfurl.py
@@ -0,0 +1,42 @@
+import wee_slack
+import pytest
+import json
+
+slack = wee_slack
+
+unfurl_map = [
+ { "input": "foo",
+ "output": "foo",
+ },
+ { "input": "<@U2147483697|@othernick>: foo",
+ "output": "@testuser: foo",
+ "ignore_alt_text": True
+ },
+ { "input": "foo <#C2147483705|#otherchannel> foo",
+ "output": "foo #otherchannel foo",
+ },
+ { "input": "foo <#C2147483705> foo",
+ "output": "foo #testchan foo",
+ },
+ { "input": "url: <https://example.com|example> suffix",
+ "output": "url: https://example.com (example) suffix",
+ },
+ { "input": "url: <https://example.com|example with spaces> suffix",
+ "output": "url: https://example.com (example with spaces) suffix",
+ },
+ ]
+
+
+def test_unfurl_refs(myservers, mychannels, myusers):
+ slack.servers = myservers
+ slack.channels = mychannels
+ slack.users = myusers
+ slack.message_cache = {}
+ slack.servers[0].users = myusers
+ print mychannels[0].identifier
+
+ for k in unfurl_map:
+ if "ignore_alt_text" in k:
+ assert slack.unfurl_refs(k["input"], ignore_alt_text=k["ignore_alt_text"]) == k["output"]
+ else:
+ assert slack.unfurl_refs(k["input"]) == k["output"]
diff --git a/wee_slack.py b/wee_slack.py
index a5b4c9e..6e1b465 100644
--- a/wee_slack.py
+++ b/wee_slack.py
@@ -4,6 +4,7 @@
from functools import wraps
import time
import json
+import os
import pickle
import sha
import re
@@ -120,6 +121,7 @@ class SlackServer(object):
self.nick = None
self.name = None
self.domain = None
+ self.server_buffer_name = None
self.login_data = None
self.buffer = None
self.token = token
@@ -201,6 +203,13 @@ class SlackServer(object):
self.domain = login_data["team"]["domain"] + ".slack.com"
dbg("connected to {}".format(self.domain))
self.identifier = self.domain
+
+ alias = w.config_get_plugin("server_alias.{}".format(login_data["team"]["domain"]))
+ if alias:
+ self.server_buffer_name = alias
+ else:
+ self.server_buffer_name = self.domain
+
self.nick = login_data["self"]["name"]
self.create_local_buffer()
@@ -241,8 +250,8 @@ class SlackServer(object):
self.buffer_prnt('{:<20} {}'.format("Team id", login_data["team"]["id"]), backlog=True)
def create_local_buffer(self):
- if not w.buffer_search("", self.domain):
- self.buffer = w.buffer_new(self.domain, "buffer_input_cb", "", "", "")
+ if not w.buffer_search("", self.server_buffer_name):
+ self.buffer = w.buffer_new(self.server_buffer_name, "buffer_input_cb", "", "", "")
w.buffer_set(self.buffer, "nicklist", "1")
w.nicklist_add_group(self.buffer, '', NICK_GROUP_HERE, "weechat.color.nicklist_group", 1)
@@ -287,6 +296,11 @@ class SlackServer(object):
item["last_read"] = 0
name = self.users.find(item["user"]).name
self.add_channel(DmChannel(self, name, item["id"], item["is_open"], item["last_read"]))
+ for item in data['self']['prefs']['muted_channels'].split(','):
+ if item == '':
+ continue
+ if self.channels.find(item) is not None:
+ self.channels.find(item).muted = True
for item in self.channels:
item.get_history()
@@ -338,6 +352,7 @@ class Channel(object):
self.last_received = None
self.messages = []
self.scrolling = False
+ self.muted = False
if active:
self.create_buffer()
self.attach_buffer()
@@ -369,11 +384,11 @@ class Channel(object):
self.members_table[user] = self.server.users.find(user)
def create_buffer(self):
- channel_buffer = w.buffer_search("", "{}.{}".format(self.server.domain, self.name))
+ channel_buffer = w.buffer_search("", "{}.{}".format(self.server.server_buffer_name, self.name))
if channel_buffer:
self.channel_buffer = channel_buffer
else:
- self.channel_buffer = w.buffer_new("{}.{}".format(self.server.domain, self.name), "buffer_input_cb", self.name, "", "")
+ self.channel_buffer = w.buffer_new("{}.{}".format(self.server.server_buffer_name, self.name), "buffer_input_cb", self.name, "", "")
if self.type == "im":
w.buffer_set(self.channel_buffer, "localvar_set_type", 'private')
else:
@@ -382,7 +397,7 @@ class Channel(object):
buffer_list_update_next()
def attach_buffer(self):
- channel_buffer = w.buffer_search("", "{}.{}".format(self.server.domain, self.name))
+ channel_buffer = w.buffer_search("", "{}.{}".format(self.server.server_buffer_name, self.name))
if channel_buffer != main_weechat_buffer:
self.channel_buffer = channel_buffer
w.buffer_set(self.channel_buffer, "localvar_set_nick", self.server.nick)
@@ -417,7 +432,7 @@ class Channel(object):
dbg("DEBUG: {} {} {}".format(self.identifier, self.name, e))
def fullname(self):
- return "{}.{}".format(self.server.domain, self.name)
+ return "{}.{}".format(self.server.server_buffer_name, self.name)
def has_user(self, name):
return name in self.members
@@ -531,17 +546,13 @@ class Channel(object):
async_slack_api_request(self.server.domain, self.server.token, SLACK_API_TRANSLATOR[self.type]["mark"], {"channel": self.identifier, "ts": time})
def rename(self):
- if current_domain_name() != self.server.domain and channels_not_on_current_server_color:
- color = w.color(channels_not_on_current_server_color)
- else:
- color = ""
if self.is_someone_typing():
new_name = ">{}".format(self.name[1:])
else:
new_name = self.name
if self.channel_buffer:
- if w.buffer_get_string(self.channel_buffer, "short_name") != (color + new_name):
- w.buffer_set(self.channel_buffer, "short_name", color + new_name)
+ if w.buffer_get_string(self.channel_buffer, "short_name") != new_name:
+ w.buffer_set(self.channel_buffer, "short_name", new_name)
# deprecated in favor of redrawing the entire buffer
# def buffer_prnt_changed(self, user, text, time, append=""):
@@ -569,7 +580,9 @@ class Channel(object):
tags = "notify_highlight"
elif user != self.server.nick and self.name in self.server.users:
tags = "notify_private,notify_message"
- elif user in [w.prefix("join"), w.prefix("quit")]:
+ elif self.muted:
+ tags = "no_highlight,notify_none,logger_backlog_end"
+ elif user in [x.strip() for x in w.prefix("join"), w.prefix("quit")]:
tags = "irc_smart_filter"
else:
tags = "notify_message"
@@ -584,7 +597,8 @@ class Channel(object):
name = name.decode('utf-8')
#colorize nicks in each line
chat_color = w.config_string(w.config_get('weechat.color.chat'))
- message = message.decode('UTF-8', 'replace')
+ if type(message) is not unicode:
+ message = message.decode('UTF-8', 'replace')
for user in self.server.users:
if user.name in message:
message = user.name_regex.sub(
@@ -683,15 +697,12 @@ class DmChannel(Channel):
self.type = "im"
def rename(self):
- if current_domain_name() != self.server.domain and channels_not_on_current_server_color:
- force_color = w.color(channels_not_on_current_server_color)
- else:
- force_color = None
+ global colorize_private_chats
if self.server.users.find(self.name).presence == "active":
- new_name = self.server.users.find(self.name).formatted_name('+', force_color)
+ new_name = self.server.users.find(self.name).formatted_name('+', colorize_private_chats)
else:
- new_name = self.server.users.find(self.name).formatted_name(' ', force_color)
+ new_name = self.server.users.find(self.name).formatted_name(' ', colorize_private_chats)
if self.channel_buffer:
w.buffer_set(self.channel_buffer, "short_name", new_name)
@@ -766,9 +777,9 @@ class User(object):
self.color = ""
self.color_name = ""
- def formatted_name(self, prepend="", force_color=None):
- if colorize_nicks:
- print_color = force_color or self.color
+ def formatted_name(self, prepend="", enable_color=True):
+ if colorize_nicks and enable_color:
+ print_color = self.color
else:
print_color = ""
return print_color + prepend + self.name
@@ -902,6 +913,26 @@ def slack_buffer_required(f):
@slack_buffer_required
+def command_upload(current_buffer, args):
+ """
+ Uploads a file to the current buffer
+ /slack upload [file_path]
+ """
+ post_data = {}
+ channel = current_buffer_name(short=True)
+ domain = current_domain_name()
+ token = servers.find(domain).token
+
+ if servers.find(domain).channels.find(channel):
+ channel_identifier = servers.find(domain).channels.find(channel).identifier
+
+ if channel_identifier:
+ post_data["token"] = token
+ post_data["channels"] = channel_identifier
+ post_data["file"] = args
+ async_slack_api_upload_request(token, "files.upload", post_data)
+
+@slack_buffer_required
def command_talk(current_buffer, args):
"""
Open a chat with the specified user
@@ -1112,7 +1143,7 @@ def command_openweb(current_buffer, args):
if trigger != "0":
if args is None:
channel = channels.find(current_buffer)
- url = "{}/messages/{}".format(channel.server.domain, channel.name)
+ url = "{}/messages/{}".format(channel.server.server_buffer_name, channel.name)
topic = w.buffer_get_string(channel.channel_buffer, "title")
w.buffer_set(channel.channel_buffer, "title", "{}:{}".format(trigger, url))
w.hook_timer(1000, 0, 1, "command_openweb", json.dumps({"topic": topic, "buffer": current_buffer}))
@@ -1169,6 +1200,19 @@ def process_pong(message_json):
pass
+def process_pref_change(message_json):
+ server = servers.find(message_json["myserver"])
+ if message_json['name'] == u'muted_channels':
+ muted = message_json['value'].split(',')
+ for c in server.channels:
+ if c.identifier in muted:
+ c.muted = True
+ else:
+ c.muted = False
+ else:
+ dbg("Preference change not implemented: {}\n{}".format(message_json['name']))
+
+
def process_team_join(message_json):
server = servers.find(message_json["myserver"])
item = message_json["user"]
@@ -1470,43 +1514,60 @@ def unwrap_attachments(message_json):
# attachment_text = attachment_text.encode('ascii', 'ignore')
return attachment_text
+def resolve_ref(ref):
+ if ref.startswith('@U'):
+ if users.find(ref[1:]):
+ try:
+ return "@{}".format(users.find(ref[1:]).name)
+ except:
+ dbg("NAME: {}".format(ref))
+ elif ref.startswith('#C'):
+ if channels.find(ref[1:]):
+ try:
+ return "{}".format(channels.find(ref[1:]).name)
+ except:
+ dbg("CHANNEL: {}".format(ref))
+
+ # Something else, just return as-is
+ return ref
+
+def unfurl_ref(ref, ignore_alt_text=False):
+ id = ref.split('|')[0]
+ display_text = ref
+ if ref.find('|') > -1:
+ if ignore_alt_text:
+ display_text = resolve_ref(id)
+ else:
+ if id.startswith("#C") or id.startswith("@U"):
+ display_text = ref.split('|')[1]
+ else:
+ url, desc = ref.split('|', 1)
+ display_text = "{} ({})".format(url, desc)
+ else:
+ display_text = resolve_ref(ref)
+ return display_text
def unfurl_refs(text, ignore_alt_text=False):
"""
Worst code ever written. this needs work
"""
if text and text.find('<') > -1:
- newtext = []
- text = text.split(" ")
- for item in text:
- # dbg(item)
- prefix = ""
- suffix = ""
- start = item.find('<')
- end = item.find('>')
- if start > -1 and end > -1:
- prefix = item[:start]
- suffix = item[end+1:]
- item = item[start + 1:end]
- if item.find('|') > -1:
- if ignore_alt_text:
- item = item.split('|')[1]
- else:
- item = item.split('|')[0]
- if item.startswith('@U'):
- if users.find(item[1:]):
- try:
- item = "@{}".format(users.find(item[1:]).name)
- except:
- dbg("NAME: {}".format(item))
- if item.startswith('#C'):
- if channels.find(item[1:]):
- item = "{}".format(channels.find(item[1:]).name)
- newtext.append(prefix + item + suffix)
- text = " ".join(newtext)
- return text
- else:
- return text
+ end = 0
+ newtext = ""
+ while text.find('<') > -1:
+ # Prepend prefix
+ newtext += text[:text.find('<')]
+ text = text[text.find('<'):]
+ end = text.find('>')
+ if end == -1:
+ newtext += text
+ break
+ # Format thingabob
+ newtext += unfurl_ref(text[1:end], ignore_alt_text)
+ text = text[end+1:]
+ newtext += text
+ return newtext
+ return text
def get_user(message_json, server):
@@ -1674,6 +1735,8 @@ def complete_next_cb(data, buffer, command):
# If we're on a non-word, look left for something to complete
while current_pos >= 0 and input[current_pos] != '@' and not input[current_pos].isalnum():
current_pos = current_pos - 1
+ if current_pos < 0:
+ current_pos = 0
for l in range(current_pos, 0, -1):
if input[l] != '@' and not input[l].isalnum():
word_start = l + 1
@@ -1695,12 +1758,21 @@ def complete_next_cb(data, buffer, command):
# Slack specific requests
-# NOTE: switched to async/curl because sync slowed down the UI
+# NOTE: switched to async because sync slowed down the UI
def async_slack_api_request(domain, token, request, post_data, priority=False):
if not STOP_TALKING_TO_SLACK:
post_data["token"] = token
- url = 'https://{}/api/{}'.format(domain, request)
- command = 'curl -A "wee_slack {}" -s --data "{}" {}'.format(SCRIPT_VERSION, urllib.urlencode(post_data), url)
+ url = 'url:https://{}/api/{}?{}'.format(domain, request, urllib.urlencode(post_data))
+ context = pickle.dumps({"request": request, "token": token, "post_data": post_data})
+ params = { 'useragent': 'wee_slack {}'.format(SCRIPT_VERSION) }
+ dbg("URL: {} context: {} params: {}".format(url, context, params))
+ w.hook_process_hashtable(url, params, 20000, "url_processor_cb", context)
+
+def async_slack_api_upload_request(token, request, post_data, priority=False):
+ if not STOP_TALKING_TO_SLACK:
+ url = 'https://slack.com/api/{}'.format(request)
+ file_path = os.path.expanduser(post_data["file"])
+ command = 'curl -F file=@{} -F channels={} -F token={} {}'.format(file_path, post_data["channels"], token, url)
context = pickle.dumps({"request": request, "token": token, "post_data": post_data})
w.hook_process(command, 20000, "url_processor_cb", context)
@@ -1718,8 +1790,8 @@ def url_processor_cb(data, command, return_code, out, err):
try:
my_json = json.loads(big_data[identifier])
except:
- dbg("curl failed, doing again...")
- dbg("curl length: {} identifier {}\n{}".format(len(big_data[identifier]), identifier, data))
+ dbg("request failed, doing again...")
+ dbg("response length: {} identifier {}\n{}".format(len(big_data[identifier]), identifier, data))
my_json = False
big_data.pop(identifier, None)
@@ -1742,9 +1814,10 @@ def url_processor_cb(data, command, return_code, out, err):
if "channel" in my_json:
if "members" in my_json["channel"]:
channels.find(my_json["channel"]["id"]).members = set(my_json["channel"]["members"])
- elif return_code != -1:
- big_data.pop(identifier, None)
- dbg("return code: {}".format(return_code))
+ else:
+ if return_code != -1:
+ big_data.pop(identifier, None)
+ dbg("return code: {}, data: {}".format(return_code, data))
return w.WEECHAT_RC_OK
@@ -1835,7 +1908,7 @@ def create_slack_debug_buffer():
def config_changed_cb(data, option, value):
- global slack_api_token, distracting_channels, channels_not_on_current_server_color, colorize_nicks, slack_debug, debug_mode, \
+ global slack_api_token, distracting_channels, colorize_nicks, colorize_private_chats, slack_debug, debug_mode, \
unfurl_ignore_alt_text
slack_api_token = w.config_get_plugin("slack_api_token")
@@ -1844,13 +1917,11 @@ def config_changed_cb(data, option, value):
slack_api_token = w.string_eval_expression(slack_api_token, {}, {}, {})
distracting_channels = [x.strip() for x in w.config_get_plugin("distracting_channels").split(',')]
- channels_not_on_current_server_color = w.config_get_plugin("channels_not_on_current_server_color")
- if channels_not_on_current_server_color == "0":
- channels_not_on_current_server_color = False
colorize_nicks = w.config_get_plugin('colorize_nicks') == "1"
debug_mode = w.config_get_plugin("debug_mode").lower()
if debug_mode != '' and debug_mode != 'false':
create_slack_debug_buffer()
+ colorize_private_chats = w.config_string_to_boolean(w.config_get_plugin("colorize_private_chats"))
unfurl_ignore_alt_text = False
if w.config_get_plugin('unfurl_ignore_alt_text') != "0":
@@ -1901,12 +1972,12 @@ if __name__ == "__main__":
w.config_set_plugin('slack_api_token', "INSERT VALID KEY HERE!")
if not w.config_get_plugin('distracting_channels'):
w.config_set_plugin('distracting_channels', "")
- if not w.config_get_plugin('channels_not_on_current_server_color'):
- w.config_set_plugin('channels_not_on_current_server_color', "0")
if not w.config_get_plugin('debug_mode'):
w.config_set_plugin('debug_mode', "")
if not w.config_get_plugin('colorize_nicks'):
w.config_set_plugin('colorize_nicks', "1")
+ if not w.config_get_plugin('colorize_private_chats'):
+ w.config_set_plugin('colorize_private_chats', "0")
if not w.config_get_plugin('trigger_value'):
w.config_set_plugin('trigger_value', "0")
if not w.config_get_plugin('unfurl_ignore_alt_text'):
@@ -1914,6 +1985,8 @@ if __name__ == "__main__":
if not w.config_get_plugin('switch_buffer_on_join'):
w.config_set_plugin('switch_buffer_on_join', "1")
+ w.config_option_unset('channels_not_on_current_server_color')
+
version = w.info_get("version_number", "") or 0
if int(version) >= 0x00040400:
legacy_mode = False