Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use aiohttp for bridging files #16

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 8 additions & 14 deletions __init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import aiohttp
import asyncio
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
import datetime
Expand Down Expand Up @@ -228,18 +229,6 @@ async def receive_slack_msg(**payload):
if 'files' in data:
files = data['files']

# TODO: When real support for 'files' is implemented,
# it should probably be in the format_attachments_for_zulip
# call.

# if 'files' in data:
# for file in data['files']:
# web_client.files_sharedPublicURL(id=file['id'])
# if msg == '':
# msg = file['permalink_public']
# else:
# msg += '\n' + file['permalink_public']

formatted_attachments = \
await slack_reformat.format_attachments_from_slack(
msg, attachments,
Expand All @@ -248,8 +237,13 @@ async def receive_slack_msg(**payload):
# Assumes that both markdown and plaintext need a newline together.
needs_leading_newline = \
(len(msg) > 0 or len(formatted_attachments['markdown']) > 0)
formatted_files = slack_reformat.format_files_from_slack(
files, needs_leading_newline, SLACK_TOKEN, self.zulip_client)

# TODO: We might not want a new client session for every message.
async with aiohttp.ClientSession() as session:
formatted_files = await slack_reformat.format_files_from_slack(
files, needs_leading_newline,
session, SLACK_TOKEN,
ZULIP_URL, aiohttp.BasicAuth(ZULIP_BOT_EMAIL, ZULIP_API_KEY))

zulip_message_text = \
msg + formatted_attachments['markdown'] + formatted_files['markdown']
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
aiohttp==3.6.2
redis==3.2.1
requests==2.22.0
slackclient==2.5.0
Expand Down
51 changes: 35 additions & 16 deletions slack_reformat.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import logging
import re
import sys
import requests
import traceback

from io import BytesIO
Expand Down Expand Up @@ -153,14 +152,17 @@ async def replace_markdown_link(m):
_SLACK_LINK_MATCH,
replace_markdown_link)


def format_files_from_slack(files, needs_leading_newline,
slack_bearer_token=None, zulip_client=None):
async def format_files_from_slack(files, needs_leading_newline,
aiohttp_session=None,
slack_bearer_token=None,
zulip_url=None,
aiohttp_zulip_basic_auth=None):
'''Given a list of files from the slack API, return both a markdown and plaintext
string representation of those files.

Assuming a bearer token and zulip client are provided, the files are mirrored to zulip
and those links are included in the markdown result.
Assuming a aiohttp Client Session, slack bearer token, root zulip URL, and an
aiohttp.BasicAuth for zulip are provided, the files are mirrored to zulip and
those links are included in the markdown result (but not the plaintext result).

This method only uses the passed in message text to determine how to format its output
caller must append as appropriate.'''
Expand All @@ -182,24 +184,41 @@ def format_files_from_slack(files, needs_leading_newline,

if 'name' in file and file['name']:
rendered_markdown_name = file['name']
if slack_bearer_token and zulip_client and 'url_private' in file and file['url_private']:
if (aiohttp_session and slack_bearer_token
and zulip_url and aiohttp_zulip_basic_auth
and 'url_private' in file and file['url_private']):
file_private_url = file['url_private']
r = requests.get(file_private_url,
headers={"Authorization": f"Bearer {slack_bearer_token}"})
if r.status_code == 200:
if file_private_url != r.url:
r = await aiohttp_session.get(file_private_url,
headers={"Authorization": f"Bearer {slack_bearer_token}"})
if r.status == 200:
if str(r.url) != file_private_url:
# we were redirected!
_LOGGER.info(
f'Apparent slack redirect from {file_private_url} to {r.url} when bridging file. Skipping.')
f'Apparent slack redirect from {file_private_url} to {str(r.url)} when bridging file. Skipping.')
else:
uploadable_file = BytesIO(r.content)
uploadable_file = BytesIO(await r.content.read())
uploadable_file.name = file['name']

response = zulip_client.upload_file(uploadable_file)
if 'uri' in response and response['uri']:
file_dict = {'file': uploadable_file}

# Because we want to use async io for this potentially long running request,
# we can't use the zulip client library. Instead, REST/OpenAPI it is.
upload_response = await aiohttp_session.post(
f'{zulip_url}/api/v1/user_uploads',
data=file_dict,
auth=aiohttp_zulip_basic_auth)

response = {}
if upload_response.status == 200:
response = await upload_response.json()
else:
_LOGGER.info(
f"Upload to Zulip Failed for {file['name']}. Code {upload_response.status}.")

if upload_response.status == 200 and 'uri' in response and response['uri']:
rendered_markdown_name = f"[{file['name']}]({response['uri']})"
else:
_LOGGER.info('Got bad response when uploading to zulip: {}'.format(response))
_LOGGER.info(f"Got bad response uploading to zulip for {file['name']}.. Body: {await upload_response.text()}")
else:
_LOGGER.info(f"Got code {r.status_code} when fetching {file_private_url} from slack.")

Expand Down
18 changes: 9 additions & 9 deletions slack_reformat_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,17 +199,17 @@ def test_format_files_from_slack(self):
# path for files.

# None case
output = slack_reformat.format_files_from_slack(None, False)
output = do_await(slack_reformat.format_files_from_slack(None, False))
self.assertEqual(output['plaintext'], '')
self.assertEqual(output['markdown'], '')

# None case, leading newline
output = slack_reformat.format_files_from_slack(None, True)
output = do_await(slack_reformat.format_files_from_slack(None, True))
self.assertEqual(output['plaintext'], '')
self.assertEqual(output['markdown'], '')

# Base case
output = slack_reformat.format_files_from_slack([], False)
output = do_await(slack_reformat.format_files_from_slack([], False))
self.assertEqual(output['plaintext'], '')
self.assertEqual(output['markdown'], '')

Expand All @@ -236,31 +236,31 @@ def test_format_files_from_slack(self):
"url_private": "https://files.slack.com/files-pri/T0000000-F0000000/filename.jpg"
# ... and many omitted fields
}
output = slack_reformat.format_files_from_slack([test_file], True)
output = do_await(slack_reformat.format_files_from_slack([test_file], True))
self.assertEqual(output['plaintext'], '\n(Bridged Message included file: filename.jpg)')
self.assertEqual(output['markdown'], '\n*(Bridged Message included file: filename.jpg)*')

# Same test, no leading newline.
output = slack_reformat.format_files_from_slack([test_file], False)
output = do_await(slack_reformat.format_files_from_slack([test_file], False))
self.assertEqual(output['plaintext'], '(Bridged Message included file: filename.jpg)')
self.assertEqual(output['markdown'], '*(Bridged Message included file: filename.jpg)*')

# Multiple files.
output = slack_reformat.format_files_from_slack([test_file, test_file], False)
output = do_await(slack_reformat.format_files_from_slack([test_file, test_file], False))
self.assertEqual(output['plaintext'],
'(Bridged Message included file: filename.jpg)\n(Bridged Message included file: filename.jpg)')
self.assertEqual(output['markdown'],
'*(Bridged Message included file: filename.jpg)*\n*(Bridged Message included file: filename.jpg)*')

# If we have a title that matches the filename, it should not be displayed.
test_file['title'] = test_filename
output = slack_reformat.format_files_from_slack([test_file], True)
output = do_await(slack_reformat.format_files_from_slack([test_file], True))
self.assertEqual(output['plaintext'], '\n(Bridged Message included file: filename.jpg)')
self.assertEqual(output['markdown'], '\n*(Bridged Message included file: filename.jpg)*')

# Add a distinct title to the above:
test_file['title'] = 'File Title'
output = slack_reformat.format_files_from_slack([test_file], True)
output = do_await(slack_reformat.format_files_from_slack([test_file], True))
self.assertEqual(output['plaintext'], '\n(Bridged Message included file: filename.jpg: \'File Title\')')
self.assertEqual(output['markdown'], '\n*(Bridged Message included file: filename.jpg: \'File Title\')*')

Expand All @@ -269,7 +269,7 @@ def test_format_files_from_slack(self):
"id": "U0000000",
"mode": "tombstone",
}
output = slack_reformat.format_files_from_slack([test_file], False)
output = do_await(slack_reformat.format_files_from_slack([test_file], False))
self.assertEqual(output['plaintext'], '')
self.assertEqual(output['markdown'], '')

Expand Down