Skip to content

Commit

Permalink
Merge pull request #22 from minds-ai/multi_folder_channel
Browse files Browse the repository at this point in the history
Per meeting settings
  • Loading branch information
MrFlynn committed Dec 22, 2018
2 parents e602be7 + 2f8991d commit 813485e
Show file tree
Hide file tree
Showing 7 changed files with 57 additions and 37 deletions.
6 changes: 2 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,12 @@ zoom:
username: "email@example.com"
delete: true
meetings:
- {id: "meeting_id" , name: "Meeting Name"}
- {id: "meeting_id2" , name: "Second Meeting Name"}
- {id: "meeting_id" , name: "Meeting Name", folder_id: "Some Google Drive Folder ID", slack_channel: "channel_name"}
- {id: "meeting_id2" , name: "Second Meeting Name", folder_id: "Some Google Drive Folder ID2", slack_channel: "channel_name2"}
drive:
credentials_json: "conf/credentials.json"
client_secret_json: "conf/client_secrets.json"
folder_id: "Some Google Drive Folder ID"
slack:
channel: "channel_name"
key: "slack_api_key"
internals:
target_folder: "/tmp"
Expand Down
27 changes: 20 additions & 7 deletions configuration/configuration_interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,21 +63,34 @@ class SlackConfig(APIConfigBase):
class ZoomConfig(APIConfigBase):
_classname = 'zoom'

def validate(self) -> bool:
"""Checks to see if all Zoom configuration parameters are valid.
This includes checking the 4 items that should be configured per meeting
:return: Checks to make sure that the meetings have all required properties
"""

if not all(
k in self.settings_dict for k in ('key', 'secret', 'username', 'delete', 'meetings')):
return False

for meeting in self.settings_dict['meetings']:
if not all(k in meeting for k in ('id', 'folder_id', 'name', 'slack_channel')):
return False

return True


class DriveConfig(APIConfigBase):
_classname = 'drive'

def validate(self) -> bool:
"""Checks to see if all parameters are valid.
:return: Checks to make sure that the secret file exists and the folder ID is not empty or
otherwise invalid.
:return: Checks to make sure that the secret file exists.
"""
files_exist = os.path.exists(self.settings_dict['client_secret_json'])
valid_folder_id = self.settings_dict['folder_id'] is not None \
and len(self.settings_dict['folder_id']) > 0

return files_exist and valid_folder_id
return files_exist


class SystemConfig(APIConfigBase):
Expand Down Expand Up @@ -114,7 +127,7 @@ def __load_config(self) -> Dict[str, Any]:
# If the error can be identified, print it to the console.
if hasattr(ye, 'problem_mark'):
log.log(logging.INFO, f'Error at position ({ye.problem_mark.line + 1}, '
f'{ye.problem_mark.column + 1})')
f'{ye.problem_mark.column + 1})')

raise SystemExit # Crash program

Expand Down
5 changes: 3 additions & 2 deletions drive/drive_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,12 @@ def setup(self):

log.log(logging.INFO, 'Drive connection established.')

def upload_file(self, file_path: str, name: str) -> str:
def upload_file(self, file_path: str, name: str, folder_id: str) -> str:
"""Uploads the given file to the specified folder id in Google Drive.
:param file_path: Path to file to upload to Google Drive.
:param name: Final name of the file
:param folder_id: The Google Drive folder to upload the file to
:return: The url of the file in Google Drive.
"""

Expand All @@ -78,7 +79,7 @@ def upload_file(self, file_path: str, name: str) -> str:
name='File error', reason=f'{file_path} could not be found.')

# Google Drive file metadata
metadata = {'name': name, 'parents': [self.drive_config.folder_id]}
metadata = {'name': name, 'parents': [folder_id]}

# Create a new upload of the recording and execute it.
media = apiclient.http.MediaFileUpload(file_path, mimetype='video/mp4')
Expand Down
19 changes: 14 additions & 5 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
# limitations under the License.
# ==============================================================================

import datetime
import logging
import os
import time
Expand Down Expand Up @@ -50,7 +51,10 @@ def download(zoom_conn: zoom.ZoomAPI, zoom_conf: config.ZoomConfig) -> List[Dict
result.append({'meeting': meeting['name'],
'file': res['filename'],
'name': name,
'date': res['date'].strftime('%B %d, %Y at %H:%M')})
'folder_id': meeting['folder_id'],
'slack_channel': meeting['slack_channel'],
'date': res['date'].strftime('%B %d, %Y at %H:%M'),
'unix': int(res['date'].replace(tzinfo=datetime.timezone.utc).timestamp())})

return result

Expand All @@ -65,12 +69,17 @@ def upload_and_notify(files: List, drive_conn: drive.DriveAPI, slack_conn: slack
for file in files:
try:
# Get url from upload function.
file_url = drive_conn.upload_file(file['file'], file['name'])
file_url = drive_conn.upload_file(file['file'], file['name'], file['folder_id'])

# The formatted date/time string to be used for older Slack clients
fall_back = f"{file['date']} UTC"

# Only post message if the upload worked.
message = f'The recording of _{file["meeting"]}_ on _{file["date"]} UTC_ is <{file_url}| ' \
f'now available>.'
slack_conn.post_message(message)
message = (f'The recording of _{file["meeting"]}_ on '
"_<!date^" + str(file['unix']) + "^{date} at {time}|" + fall_back + ">_"
f' is <{file_url}| now available>.')

slack_conn.post_message(message, file['slack_channel'])
except drive.DriveAPIException as e:
raise e
# Remove the file after uploading so we do not run out of disk space in our container.
Expand Down
6 changes: 2 additions & 4 deletions slack/slack_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
# ==============================================================================

import logging
from typing import Union, TypeVar, cast
from typing import TypeVar, cast

from slackclient import SlackClient
from configuration import SlackConfig, APIConfigBase
Expand All @@ -32,14 +32,12 @@ def __init__(self, config: S):
self.config = cast(SlackConfig, config)
self.sc = SlackClient(self.config.key)

def post_message(self, text: str, channel: Union[str, int] = None):
def post_message(self, text: str, channel: str):
"""Sends message to specific Slack channel with given payload.
:param text: message to sent to Slack channel.
:param channel: channel name or ID to send `text` to.
:return: None.
"""
if not channel:
channel = self.config.channel
self.sc.api_call('chat.postMessage', channel=channel, text=text)
log.log(logging.INFO, 'Slack notification sent.')
20 changes: 10 additions & 10 deletions tests/test_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def test_validation(self):
self.assertTrue(self.slack.validate())

def test_getattr(self):
self.assertEqual(self.slack.channel_name, 'some_channel')
self.assertEqual(self.slack.key, 'random_key')
with self.assertRaises(KeyError):
# pylint: disable=unused-variable
test_value = self.slack.random_value
Expand All @@ -80,8 +80,10 @@ def test_getattr(self):
self.assertEqual(self.zoom.username, 'some@email.com')
self.assertTrue(self.zoom.delete)
self.assertEqual(self.zoom.meetings,
[{'id': 'first_id', 'name': 'meeting1'},
{'id': 'second_id', 'name': 'meeting2'}]
[{'id': 'first_id', 'name': 'meeting1',
'folder_id': 'folder1', 'slack_channel': 'channel-1'},
{'id': 'second_id', 'name': 'meeting2',
'folder_id': 'folder2', 'slack_channel': 'channel-2'}]
)

with self.assertRaises(KeyError):
Expand Down Expand Up @@ -158,14 +160,14 @@ def setUp(self):
' username: "email@example.com"\n'
' delete: true\n'
' meetings:\n'
' - {id: "meeting_id" , name: "Meeting Name"}\n'
' - {id: "meeting_id2" , name: "Second Meeting Name"}\n'
' - {id: "meeting_id" , name: "Meeting Name", folder_id: "folder-id",\
slack_channel: channel_name"}\n'
' - {id: "meeting_id2" , name: "Second Meeting Name", folder_id: "folder-id2",\
slack_channel: channel_name2"}\n'
'drive:\n'
' credentials_json: "/tmp/credentials.json"\n'
f' client_secret_json: "{self.secrets_file_name}"\n'
' folder_id: "Some Google Drive Folder ID"\n'
'slack:\n'
' channel: "channel_name"\n'
' key: "slack_api_key"\n'
'internals:\n'
' target_folder: /tmp'
Expand Down Expand Up @@ -211,14 +213,12 @@ def test_bad_config(self):
username: "email@example.com"
delete: true
meetings:
- {id: "meeting_id" , name: "Meeting Name"}
- {id: "meeting_id" , name: "Meeting Name", folder_id: "FolderID", slack_channel: "name"}
- {id: "meeting_id2" , name: "Second Meeting Name"}
drive:
credentials_json: "/tmp/credentials.json"
client_secret_json: "/tmp/client_secrets.json"
folder_id: "Some Google Drive Folder ID"
slack:
channel: "channel_name"
key: "slack_api_key"
internals:
target_folder: /tmp
Expand Down
11 changes: 6 additions & 5 deletions tests/unittest_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,17 @@

class TestSettingsBase(unittest.TestCase):
def setUp(self):
self.slack_config = {'channel_name': 'some_channel', 'key': 'random_key'}
self.slack_config = {'key': 'random_key'}
self.zoom_config = {'key': 'some_key',
'secret': 'some_secret',
'username': 'some@email.com',
'delete': True,
'meetings': [
{'id': 'first_id', 'name': 'meeting1'},
{'id': 'second_id', 'name': 'meeting2'}
{'id': 'first_id', 'name': 'meeting1',
'folder_id': 'folder1', 'slack_channel': 'channel-1'},
{'id': 'second_id', 'name': 'meeting2',
'folder_id': 'folder2', 'slack_channel': 'channel-2'}
]}
self.drive_config = {'credentials_json': '/tmp/credentials.json',
'client_secret_json': '/tmp/client_secrets.json',
'folder_id': 'some_id'}
'client_secret_json': '/tmp/client_secrets.json'}
self.internal_config = {'target_folder': '/tmp'}

0 comments on commit 813485e

Please sign in to comment.