aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjason2016-07-30 17:34:05 -0600
committerjason2016-07-30 17:34:05 -0600
commit68f1e25218c19a8addd897d5f3e8bc02ae3c2edd (patch)
treefbc7847fcc69a68725d67d53d23e4657d5c660b8
parent6c36d2c2f8e8a58e9db8fe1d29a53a95c89c6879 (diff)
downloadwarmachine-ng-68f1e25218c19a8addd897d5f3e8bc02ae3c2edd.tar.gz
warmachine-ng-68f1e25218c19a8addd897d5f3e8bc02ae3c2edd.zip
standup with a timer
-rwxr-xr-xbin/dbolla14
-rw-r--r--warmachine/addons/standup.py60
-rw-r--r--warmachine/connections/base.py7
-rw-r--r--warmachine/connections/slack.py44
4 files changed, 115 insertions, 10 deletions
diff --git a/bin/dbolla b/bin/dbolla
index 7a1eba6..2b4fe02 100755
--- a/bin/dbolla
+++ b/bin/dbolla
@@ -1,6 +1,7 @@
1#!/usr/bin/env python3 1#!/usr/bin/env python3
2# -*- mode: python -*- 2# -*- mode: python -*-
3import asyncio 3import asyncio
4import datetime
4import functools 5import functools
5import logging.config 6import logging.config
6 7
@@ -49,7 +50,8 @@ class Bot(object):
49 50
50 self.loaded_plugins = [] 51 self.loaded_plugins = []
51 52
52 self.load_plugin('asdf') 53 self.load_plugin('warmachine.addons.giphy.GiphySearch')
54 self.load_plugin('warmachine.addons.standup.StandUpPlugin')
53 55
54 def start(self): 56 def start(self):
55 for connection in self.connections: 57 for connection in self.connections:
@@ -75,18 +77,22 @@ class Bot(object):
75 77
76 for p in self.loaded_plugins: 78 for p in self.loaded_plugins:
77 self.log.debug('Calling {}'.format(p.__class__.__name__)) 79 self.log.debug('Calling {}'.format(p.__class__.__name__))
78 await p.recv_msg(connection, message) 80 try:
81 await p.recv_msg(connection, message)
82 except Exception as e:
83 self.log.exception(e)
84 continue
79 85
80 self.log.debug('MSG {}: {}'.format( 86 self.log.debug('MSG {}: {}'.format(
81 connection.__class__.__name__, message)) 87 connection.__class__.__name__, message))
82 88
83 def load_plugin(self, path): 89 def load_plugin(self, class_path):
84 """ 90 """
85 Loads plugins 91 Loads plugins
86 """ 92 """
87 from importlib import import_module 93 from importlib import import_module
88 94
89 mod_path, cls_name = 'warmachine.addons.giphy.GiphySearch'.rsplit('.', 1) 95 mod_path, cls_name = class_path.rsplit('.', 1)
90 96
91 mod = import_module(mod_path) 97 mod = import_module(mod_path)
92 98
diff --git a/warmachine/addons/standup.py b/warmachine/addons/standup.py
index a21efdd..7d1ec99 100644
--- a/warmachine/addons/standup.py
+++ b/warmachine/addons/standup.py
@@ -1,3 +1,7 @@
1import asyncio
2from datetime import datetime, timedelta
3import functools
4
1from .base import WarMachinePlugin 5from .base import WarMachinePlugin
2 6
3 7
@@ -9,6 +13,11 @@ class StandUpPlugin(WarMachinePlugin):
9 !standup-add <24 hr time to kick off> <SunMTWThFSat> [channel] 13 !standup-add <24 hr time to kick off> <SunMTWThFSat> [channel]
10 !standup-remove [channel] 14 !standup-remove [channel]
11 """ 15 """
16 def __init__(self, *args, **kwargs):
17 super().__init__(*args, **kwargs)
18
19 self.standup_schedules = {}
20
12 async def recv_msg(self, connection, message): 21 async def recv_msg(self, connection, message):
13 if not message['message'].startswith('!standup'): 22 if not message['message'].startswith('!standup'):
14 return 23 return
@@ -18,11 +27,52 @@ class StandUpPlugin(WarMachinePlugin):
18 cmd = message['message'].split(' ')[0] 27 cmd = message['message'].split(' ')[0]
19 parts = message['message'].split(' ')[1:] 28 parts = message['message'].split(' ')[1:]
20 29
30 self._loop = asyncio.get_event_loop()
31
21 if cmd == '!standup-add': 32 if cmd == '!standup-add':
22 await connection.say('Scheduling standup for {} on {}'.format( 33 pretty_next_standup, next_standup_secs = \
23 parts[1], parts[2])) 34 self.get_next_standup_secs(parts[0])
35
36 f = self._loop.call_later(
37 next_standup_secs, functools.partial(
38 self.standup_schedule_func, connection, message['channel']))
39
40 self.standup_schedules[message['channel']] = {
41 'future': f,
42 }
43 await connection.say('Next standup in {}'.format(
44 pretty_next_standup), message['channel'])
45 await connection.say(str(self.standup_schedules),
46 message['channel'])
47
48 def standup_schedule_func(self, connection, channel):
49 asyncio.ensure_future(self.start_standup(connection, channel))
50
51 async def start_standup(self, connection, channel):
52 await connection.say('@channel Time for standup', channel)
53 connection.get_users_by_channel(channel)
54
55 @classmethod
56 def get_next_standup_secs(cls, time24h):
57 """
58 calculate the number of seconds until the next standup time
59 """
60 now = datetime.now()
61
62 # if it's friday, wait 72 hours
63 if now.isoweekday() == 5:
64 hours = 72
65 # if it's saturday, wait 48
66 elif now.isoweekday() == 6:
67 hours = 48
68 # if it's sunday-thur wait 24
69 else:
70 hours = 24
24 71
25 # await connection.say('{}, {}'.format(cmd, parts), message['channel']) 72 standup_hour, standup_minute = (int(s) for s in time24h.split(':'))
26 73
27 async def start_standup(self, connection): 74 future = now + timedelta(hours=hours)
28 pass 75 next_standup = datetime(future.year, future.month, future.day,
76 standup_hour, standup_minute)
77 standup_in = next_standup-now
78 return standup_in, standup_in.seconds
diff --git a/warmachine/connections/base.py b/warmachine/connections/base.py
index e68e9b9..4df5996 100644
--- a/warmachine/connections/base.py
+++ b/warmachine/connections/base.py
@@ -26,6 +26,13 @@ class Connection(object):
26 raise NotImplementedError('{} must implement `read` method'.format( 26 raise NotImplementedError('{} must implement `read` method'.format(
27 self.__class__.__name__)) 27 self.__class__.__name__))
28 28
29 def get_users_by_channel(self, channel):
30 """
31 Return a list of users who are in the provided channel
32 """
33 raise NotImplementedError('{} must implement `get_users_by_channel` '
34 'method'.format(self.__class__.__name__))
35
29 def id(self): 36 def id(self):
30 """ 37 """
31 Unique ID for this connection. Since there can be more than one 38 Unique ID for this connection. Since there can be more than one
diff --git a/warmachine/connections/slack.py b/warmachine/connections/slack.py
index b1a5390..90903e7 100644
--- a/warmachine/connections/slack.py
+++ b/warmachine/connections/slack.py
@@ -3,6 +3,7 @@ import json
3import logging 3import logging
4from pprint import pformat 4from pprint import pformat
5from urllib.parse import urlencode 5from urllib.parse import urlencode
6import urllib.request
6 7
7import websockets 8import websockets
8 9
@@ -95,7 +96,6 @@ class SlackWS(Connection):
95 Returns: 96 Returns:
96 str: websocket url to connect to 97 str: websocket url to connect to
97 """ 98 """
98 import urllib.request
99 url = 'https://slack.com/api/rtm.start?{}'.format( 99 url = 'https://slack.com/api/rtm.start?{}'.format(
100 urlencode( 100 urlencode(
101 {'token': 101 {'token':
@@ -121,6 +121,8 @@ class SlackWS(Connection):
121 """ 121 """
122 if not self._info: 122 if not self._info:
123 return 123 return
124 with open('slack_info.json', 'w') as f:
125 f.write(pformat(self._info))
124 126
125 self.status = CONNECTED 127 self.status = CONNECTED
126 128
@@ -190,6 +192,18 @@ class SlackWS(Connection):
190 )) 192 ))
191 self.user_map[msg['user']]['presence'] = msg['presence'] 193 self.user_map[msg['user']]['presence'] = msg['presence']
192 194
195 def get_users_by_channel(self, channel):
196 url = 'https://slack.com/api/groups.info?{}'.format(urlencode(
197 {
198 'token': self.token,
199 'channel': channel,
200 }))
201 self.log.debug(url)
202 req = urllib.request.Request(url)
203 r = urllib.request.urlopen(req).read().decode('utf-8')
204
205 self.log.debug(r)
206
193 async def on_group_join(self, channel): 207 async def on_group_join(self, channel):
194 """ 208 """
195 The group_joined event is sent to all connections for a user when that 209 The group_joined event is sent to all connections for a user when that
@@ -279,3 +293,31 @@ class SlackWS(Connection):
279 # 'user': 'U1U05AF5J' 293 # 'user': 'U1U05AF5J'
280 # } 294 # }
281 # } 295 # }
296
297
298# Invited to a public channel
299# 2016-07-29 16:23:24,817 [DEBUG] SlackWS: on_channel_joined does not exist for message: {'type': 'channel_joined', 'chan
300# nel': {'members': ['U0286NL58', 'U1U05AF5J'], 'purpose': {'last_set': 0, 'creator': '', 'value': ''}, 'topic': {'last_s
301# et': 0, 'creator': '', 'value': ''}, 'is_member': True, 'is_channel': True, 'creator': 'U0286NL58', 'is_archived': Fals
302# e, 'unread_count_display': 0, 'id': 'C1WJU3ZU0', 'name': 'wm-test2', 'is_general': False, 'created': 1469830985, 'unrea
303# d_count': 0, 'latest': {'text': '<@U0286NL58|jason> has joined the channel', 'type': 'message', 'user': 'U0286NL58', 's
304# ubtype': 'channel_join', 'ts': '1469830985.000002'}, 'last_read': '1469830985.000002'}}
305# 2016-07-29 16:23:24,878 [DEBUG] SlackWS: on_message_channel_join does not exist for message: {'channel': 'C1WJU3ZU0', '
306# text': '<@U1U05AF5J|wm-standup-test> has joined the channel', 'type': 'message', 'inviter': 'U0286NL58', 'subtype': 'ch
307# annel_join', 'user_profile': {'real_name': '', 'name': 'wm-standup-test', 'image_72': 'https://avatars.slack-edge.com/2
308# 016-07-21/62015427159_1da65a3cf7a85e85c3cb_72.png', 'first_name': None, 'avatar_hash': '1da65a3cf7a8'}, 'ts': '14698310
309# 04.000003', 'user': 'U1U05AF5J', 'team': 'T027XPE12'}
310
311# Someone else joins a public channel
312# 2016-07-29 16:26:19,966 [DEBUG] SlackWS: on_message_channel_join does not exist for message: {'type': 'message', 'invit
313# er': 'U0286NL58', 'ts': '1469831179.000004', 'team': 'T027XPE12', 'user': 'U0286167T', 'channel': 'C1WJU3ZU0', 'user_pr
314# ofile': {'name': 'synic', 'image_72': 'https://avatars.slack-edge.com/2016-06-24/54136624065_49ec8bc368966c152817_72.jp
315# g', 'real_name': 'Adam Olsen', 'first_name': 'Adam', 'avatar_hash': '49ec8bc36896'}, 'subtype': 'channel_join', 'text':
316# '<@U0286167T|synic> has joined the channel'}
317
318# Invited to a private channel
319# 2016-07-29 16:27:29,376 [DEBUG] SlackWS: on_message_group_join does not exist for message: {'type': 'message', 'inviter
320# ': 'U0286NL58', 'ts': '1469831249.000047', 'team': 'T027XPE12', 'user': 'U0286167T', 'channel': 'G1W837CGP', 'user_prof
321# ile': {'name': 'synic', 'image_72': 'https://avatars.slack-edge.com/2016-06-24/54136624065_49ec8bc368966c152817_72.jpg'
322# , 'real_name': 'Adam Olsen', 'first_name': 'Adam', 'avatar_hash': '49ec8bc36896'}, 'subtype': 'group_join', 'text': '<@
323# U0286167T|synic> has joined the group'}