From b99ccff4e2f49acd723ea08f2b73224a4b24d629 Mon Sep 17 00:00:00 2001 From: JacobJ-Git Date: Mon, 13 Nov 2017 21:12:35 -0600 Subject: [PATCH 01/10] developing under develop now switched to dev branch, back to InMemoryStorage only --- hey_fireball.py | 54 +++++++++++++++++++++++++++++++++++++++++++------ storage.py | 24 +++++++++++++++++++++- 2 files changed, 71 insertions(+), 7 deletions(-) diff --git a/hey_fireball.py b/hey_fireball.py index d445cf3..20e7196 100644 --- a/hey_fireball.py +++ b/hey_fireball.py @@ -31,7 +31,7 @@ # instantiate Slack & Twilio clients slack_client = SlackClient(os.environ.get('SLACK_BOT_TOKEN')) -commands = ['leaderboard', 'fullboard', POINTS, '{}left'.format(POINTS)] +commands = ['leaderboard', 'fullboard', POINTS, '{}left'.format(POINTS), 'setpm'] commands_with_target = [POINTS, 'all'] user_list = slack_client.api_call("users.list")['members'] @@ -80,6 +80,7 @@ def __init__(self, msg): self.target_name = self.target_id self.command = self._extract_command() self.count = self._extract_count() + self.setting = self._extract_setting() def __str__(self): return str(vars(self)) @@ -149,6 +150,27 @@ def _extract_count(self): return int(self.parts[idx]) except ValueError: pass + + def _extract_setting(self): + if self.bot_is_first: + idx = 2 + try: + self.parts[idx] + except IndexError: + # Act as a toggle + if get_pm_preference(self.requestor_id): + print('setting off via toggle') + return 0 + else: + print('setting on via toggle') + return 1 + if self.parts[idx].lower() == 'on': + print('setting on via command') + return 1 + elif self.parts[idx].lower() == 'off': + print('setting off via command') + return 0 + ''' # Use the following to catch and handle missing methods/properties as we want def __getattr__(self, name): @@ -205,6 +227,14 @@ def get_users_and_scores() -> list: """Return list of (user, total points received) tuples.""" return _storage.get_users_and_scores_total() +def get_pm_preference(user_id: str) -> int: + """Return user's PM Preference""" + return _storage.get_pm_preference(user_id) + +def set_pm_preference(user_id: str, pref: int): + """Set user's PM Preference""" + _storage.set_pm_preference(user_id, pref) + ##################### # Parsing Message @@ -277,7 +307,6 @@ def handle_command(fireball_message): if SELF_POINTS == 'DISALLOW' and (fireball_message.requestor_id == fireball_message.target_id): msg = 'You cannot give points to yourself!' send_message_to = fireball_message.requestor_id_only - # Determine if requestor has enough points to give. elif check_points(fireball_message.requestor_id, fireball_message.count): # Add points to target score. @@ -322,15 +351,28 @@ def handle_command(fireball_message): points_rmn = get_user_points_remaining(fireball_message.requestor_id) msg = f"You have {points_rmn} {POINTS} remaining" send_message_to = fireball_message.requestor_id_only + + elif fireball_message.command == 'setpm': + setting = fireball_message.setting + set_pm_preference(fireball_message.requestor_id, setting) + if setting: + msg = "PM Preference: On" + else: + msg = "PM Preference: Off" + send_message_to = fireball_message.requestor_id_only else: - # Message was not valid, so + # Message was not valid, so msg = f'{fireball_message.requestor_id}: I do not understand your message. Try again!' send_message_to = fireball_message.channel - # Post message to Slack. - slack_client.api_call("chat.postMessage", channel=send_message_to, - text=msg, as_user=True, attachments=attach) + if get_pm_preference(fireball_message.requestor_id_only) == 0 and send_message_to == fireball_message.requestor_id_only: + slack_client.api_call("chat.postEphemeral", channel=fireball_message.channel, + text=msg, user=fireball_message.requestor_id_only, + as_user=True, attachments=attach) + else: + slack_client.api_call("chat.postMessage", channel=send_message_to, + text=msg, as_user=True, attachments=attach) def give_fireball(user_id, number_of_points): diff --git a/storage.py b/storage.py index 66a3fb5..7d47659 100644 --- a/storage.py +++ b/storage.py @@ -58,6 +58,15 @@ def get_users_and_scores_total(self): """Return list of tuples (user_id, points_received_total).""" pass + ### PM Preferences + def get_pm_preference(self, user_id: str): + """Return user's PM Preference""" + pass + + def set_pm_preference(self, user_id: str, pref: int): + """Set user's PM Preference""" + pass + class AzureTableStorage(Storage): """Implementation of `Storage` that uses Azure Table Service. @@ -370,6 +379,7 @@ class InMemoryStorage(Storage): POINTS_USED = 'POINTS_USED' POINTS_RECEIVED = 'POINTS_RECEIVED' + PM_PREFERENCE = 'PM_PREFERENCE' def __init__(self): super().__init__() @@ -385,7 +395,8 @@ def create_user_entry(self, user_id: str): """Create new user entry and init fields.""" self._data[user_id] = { self.POINTS_USED : 0, - self.POINTS_RECEIVED : 0 + self.POINTS_RECEIVED : 0, + self.PM_PREFERENCE: 1 } def user_exists(self, user_id: str): @@ -417,3 +428,14 @@ def add_user_points_received(self, user_id: str, num: int): def get_users_and_scores(self): """Return list of tuples (user_id, points_received).""" return [(k, v[self.POINTS_RECEIVED]) for k,v in self._data.items()] + + def get_pm_preference(self, user_id: str): + """Return user's PM Preference""" + self.check_user(user_id=user_id) + return self._data[user_id].get(self.PM_PREFERENCE) + + def set_pm_preference(self, user_id: str, pref: int): + """Set user's PM Preference""" + self.check_user(user_id=user_id) + user_data = self._data.setdefault(user_id, {}) + user_data[self.PM_PREFERENCE] = pref From b0d76ab8a074f1e610d8848ad9ff3512d94084c4 Mon Sep 17 00:00:00 2001 From: JacobJ-Git Date: Mon, 13 Nov 2017 21:35:09 -0600 Subject: [PATCH 02/10] housekeeping Removed debugging lines, got rid of/commented out unused code --- hey_fireball.py | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/hey_fireball.py b/hey_fireball.py index 20e7196..931efb7 100644 --- a/hey_fireball.py +++ b/hey_fireball.py @@ -159,16 +159,12 @@ def _extract_setting(self): except IndexError: # Act as a toggle if get_pm_preference(self.requestor_id): - print('setting off via toggle') return 0 else: - print('setting on via toggle') return 1 if self.parts[idx].lower() == 'on': - print('setting on via command') return 1 elif self.parts[idx].lower() == 'off': - print('setting off via command') return 0 ''' @@ -315,7 +311,7 @@ def handle_command(fireball_message): add_user_points_used(fireball_message.requestor_id, fireball_message.count) msg = f'You received {fireball_message.count} {POINTS} from {fireball_message.requestor_name}' send_message_to = fireball_message.target_id_only - + else: # Requestor lacks enough points to give. msg = f'You do not have enough {POINTS}!' @@ -353,9 +349,8 @@ def handle_command(fireball_message): send_message_to = fireball_message.requestor_id_only elif fireball_message.command == 'setpm': - setting = fireball_message.setting - set_pm_preference(fireball_message.requestor_id, setting) - if setting: + set_pm_preference(fireball_message.requestor_id, fireball_message.setting) + if fireball_message.setting: msg = "PM Preference: On" else: msg = "PM Preference: Off" @@ -366,7 +361,7 @@ def handle_command(fireball_message): msg = f'{fireball_message.requestor_id}: I do not understand your message. Try again!' send_message_to = fireball_message.channel # Post message to Slack. - if get_pm_preference(fireball_message.requestor_id_only) == 0 and send_message_to == fireball_message.requestor_id_only: + if not get_pm_preference(fireball_message.requestor_id_only) and send_message_to == fireball_message.requestor_id_only: slack_client.api_call("chat.postEphemeral", channel=fireball_message.channel, text=msg, user=fireball_message.requestor_id_only, as_user=True, attachments=attach) @@ -375,16 +370,16 @@ def handle_command(fireball_message): text=msg, as_user=True, attachments=attach) -def give_fireball(user_id, number_of_points): - """Add `number_of_points` to `user_id`'s total score. - """ - add_user_points_received(user_id, number_of_points) +# def give_fireball(user_id, number_of_points): +# """Add `number_of_points` to `user_id`'s total score. +# """ +# add_user_points_received(user_id, number_of_points) -def remove_points(user_id, number_of_points): - """ - """ - pass +# def remove_points(user_id, number_of_points): +# """ +# """ +# pass def check_points(user_id, number_of_points): From efbd25cf763067012dea70267e3d13aec13aea6b Mon Sep 17 00:00:00 2001 From: JacobJ-Git Date: Tue, 14 Nov 2017 08:14:52 -0600 Subject: [PATCH 03/10] formatting corrections. pm preference in azure and InMemoryStorage Remove spaces. PM Preference options are now in Azure, Redis, and InMemoryStorage. Testing is needed for Azure, and Redis --- storage.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/storage.py b/storage.py index 7d47659..c822ed2 100644 --- a/storage.py +++ b/storage.py @@ -144,8 +144,8 @@ def _user_exists(self, user_id: str) -> bool: if self._users is None: filter_query = "PartitionKey eq '{partition}'".format(partition=self.TOTAL_PARTITION) records = self._table_service.query_entities(self._table_name, - filter=filter_query, - select='RowKey') + filter=filter_query, + select='RowKey') self._users = {r['RowKey'] for r in records} return user_id in self._users @@ -158,8 +158,8 @@ def _check_user(self, user_id: str): def _move_user_to_new_day(self, user_id: str): """Save the daily record and reset daily counts on Total partion.""" total_record = self._table_service.get_entity(self._table_name, - self.TOTAL_PARTITION, - user_id) + self.TOTAL_PARTITION, + user_id) del total_record['etag'] self._save_daily_record(total_record) self._reset_daily_counts(total_record) From 9ddd4d268da03b02bd039d639afa2404adcce8f5 Mon Sep 17 00:00:00 2001 From: JacobJ-Git Date: Tue, 14 Nov 2017 09:09:24 -0600 Subject: [PATCH 04/10] bot replies fixed supporting pm_preference if/elif/else clause to handle user's pm preference options when the bot needs to send a message --- hey_fireball.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/hey_fireball.py b/hey_fireball.py index 931efb7..df44f24 100644 --- a/hey_fireball.py +++ b/hey_fireball.py @@ -347,7 +347,7 @@ def handle_command(fireball_message): points_rmn = get_user_points_remaining(fireball_message.requestor_id) msg = f"You have {points_rmn} {POINTS} remaining" send_message_to = fireball_message.requestor_id_only - + elif fireball_message.command == 'setpm': set_pm_preference(fireball_message.requestor_id, fireball_message.setting) if fireball_message.setting: @@ -361,10 +361,16 @@ def handle_command(fireball_message): msg = f'{fireball_message.requestor_id}: I do not understand your message. Try again!' send_message_to = fireball_message.channel # Post message to Slack. - if not get_pm_preference(fireball_message.requestor_id_only) and send_message_to == fireball_message.requestor_id_only: + if (send_message_to == fireball_message.requestor_id_only and + get_pm_preference(fireball_message.requestor_id) == 0): slack_client.api_call("chat.postEphemeral", channel=fireball_message.channel, text=msg, user=fireball_message.requestor_id_only, - as_user=True, attachments=attach) + attachments=attach) + elif (send_message_to == fireball_message.target_id and + get_pm_preference(fireball_message.target_id) == 0): + slack_client.api_call("chat.postEphemeral", channel=fireball_message.channel, + text=msg, user=fireball_message.target_id_only, + attachments=attach) else: slack_client.api_call("chat.postMessage", channel=send_message_to, text=msg, as_user=True, attachments=attach) From c8b69dcdea59912431fb76606254d00c9caf19da Mon Sep 17 00:00:00 2001 From: JacobJ-Git Date: Tue, 14 Nov 2017 10:55:47 -0600 Subject: [PATCH 05/10] Azure and Redis added Something goofy happened previously. PM Preference options are coded out for Azure and Redis storage options now. Testing still needed. --- storage.py | 42 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/storage.py b/storage.py index c822ed2..3c14ef4 100644 --- a/storage.py +++ b/storage.py @@ -108,6 +108,7 @@ class AzureTableStorage(Storage): NEGATIVE_POINTS_USED_TODAY = 'NEGATIVE_POINTS_USED_TODAY' USERS_LIST = 'USERS_LIST' TOTAL_PARTITION = 'TOTAL' + PM_PREFERENCE = 'PM_PREFERENCE' def __init__(self): super().__init__() @@ -136,7 +137,8 @@ def _create_user_entry(self, user_id: str): self.NEGATIVE_POINTS_USED_TOTAL: 0, self.POINTS_RECEIVED_TODAY: 0, self.POINTS_USED_TODAY: 0, - self.NEGATIVE_POINTS_USED_TODAY: 0}) + self.NEGATIVE_POINTS_USED_TODAY: 0, + self.PM_PREFERENCE: 1}) self._users.add(user_id) def _user_exists(self, user_id: str) -> bool: @@ -280,7 +282,7 @@ def add_user_points_received(self, user_id: str, num: int): # This record is from a previous day, so need to update table. self._move_user_to_new_day(user_id) # Since the record was old, there are 0 Daily points. - record[self.POINTS_RECEIVED_TODAY] = num + record[self.POINTS_RECEIVED_TODAY] = num else: # The record is current, so update Daily count. record[self.POINTS_RECEIVED_TODAY] += num @@ -293,10 +295,31 @@ def get_users_and_scores_total(self) -> list: filter_query = "PartitionKey eq '{}'".format(self.TOTAL_PARTITION) select_query = "Timestamp,RowKey,{}".format(self.POINTS_RECEIVED_TOTAL) records = self._table_service.query_entities(self._table_name, - filter=filter_query, - select=select_query) + filter=filter_query, + select=select_query) return [(r['RowKey'], r[self.POINTS_RECEIVED_TOTAL]) for r in records] + def set_pm_preference(self, user_id: str, pref: int): + """Set the user's PM Preference""" + self._check_user(user_id) + select_query = "PartitionKey,RowKey,Timestamp,{}".format(self.PM_PREFERENCE) + record = self._table_service.get_entity(self._table_name, + partition_key=self.TOTAL_PARTITION, + row_key=user_id, + select=select_query) + # del record['etag'] # Need to read up on this + record[self.PM_PREFERENCE] = pref + + def get_pm_preference(self, user_id: str) -> int: + """Return user's PM Preference integer. 0 = no pm's, 1 = all pm's""" + self._check_user(user_id) + select_query = "PartitionKey,RowKey,Timestamp,{}".format(self.PM_PREFERENCE) + record = self._table_service.get_entity(self._table_name, + partition_key=self.TOTAL_PARTITION, + row_key=user_id, + select=select_query) + return record[self.PM_PREFERENCE] + @staticmethod def _get_today() -> datetime.date: """Return today's date as a string YYYY-MM-DD.""" @@ -344,7 +367,7 @@ def _check_date(ts: datetime.datetime) -> bool: # def _create_user_entry(self, user_id: str): # """Create new user entry and init fields.""" - # self._redis.hmset(user_id, {self.POINTS_USED:0, self.POINTS_RECEIVED:0}) + # self._redis.hmset(user_id, {self.POINTS_USED:0, self.POINTS_RECEIVED:0, self.PM_PREFERENCE:1}) # self._redis.sadd(self.USERS_LIST_KEY, user_id) # def user_exists(self, user_id: str): @@ -372,6 +395,13 @@ def _check_date(ts: datetime.datetime) -> bool: # users = self._redis.smembers(self.USERS_LIST_KEY) # return [(user, self.get_user_points_received(user)) for user in users] + # def get_pm_preference(self, user_id: str) -> int: + # """Return integer of user's PM preference. 0 = no pm's. 1 = all pm's""" + # return int(self._redis.hget(user_id, self.PM_PREFERENCE)) + + # def set_pm_preference(self, user_id: str, pref: int): + # """Set's the user's PM Preference""" + # self._redis.hmset(user_id, {self.PM_PREFERENCE:pref}) class InMemoryStorage(Storage): """Implementation of `Storage` that uses a dict in memory. @@ -429,7 +459,7 @@ def get_users_and_scores(self): """Return list of tuples (user_id, points_received).""" return [(k, v[self.POINTS_RECEIVED]) for k,v in self._data.items()] - def get_pm_preference(self, user_id: str): + def get_pm_preference(self, user_id: str) -> int: """Return user's PM Preference""" self.check_user(user_id=user_id) return self._data[user_id].get(self.PM_PREFERENCE) From 242f94addf5077d7c897c2d871c37953b7e8985a Mon Sep 17 00:00:00 2001 From: JacobJ-Git Date: Tue, 14 Nov 2017 11:01:33 -0600 Subject: [PATCH 06/10] DS_Store ignore it --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index d3b62e4..09f7086 100644 --- a/.gitignore +++ b/.gitignore @@ -104,4 +104,5 @@ ENV/ .vscode # custom -*.ignoreme \ No newline at end of file +*.ignoreme +.DS_Store From ca0d7ace1ed7a0b72248e52d39220aff8b6614aa Mon Sep 17 00:00:00 2001 From: JacobJ Date: Wed, 15 Nov 2017 20:57:15 -0600 Subject: [PATCH 07/10] small fixes/issue prevention --- hey_fireball.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/hey_fireball.py b/hey_fireball.py index df44f24..b6650f8 100644 --- a/hey_fireball.py +++ b/hey_fireball.py @@ -19,7 +19,6 @@ BOT_ID = os.environ.get("BOT_ID") EMOJI = os.environ.get('EMOJI') POINTS = os.environ.get('POINTS') -SELF_POINTS = os.environ.get('SELF_POINTS', "DISALLOW") # constants AT_BOT = "<@" + BOT_ID + ">" @@ -81,6 +80,8 @@ def __init__(self, msg): self.command = self._extract_command() self.count = self._extract_count() self.setting = self._extract_setting() + self.setting = self._extract_setting() # Find on/off or assume toggle + self.ts = msg['ts'] # Store the thread_ts def __str__(self): return str(vars(self)) @@ -154,18 +155,24 @@ def _extract_count(self): def _extract_setting(self): if self.bot_is_first: idx = 2 + curPref = get_pm_preference(self.requestor_id) try: self.parts[idx] except IndexError: # Act as a toggle if get_pm_preference(self.requestor_id): + if curPref: return 0 else: return 1 if self.parts[idx].lower() == 'on': + if self.parts[idx].lower() == 'on' and not curPref: return 1 elif self.parts[idx].lower() == 'off': + elif self.parts[idx].lower() == 'off' and curPref: return 0 + else: + pass ''' # Use the following to catch and handle missing methods/properties as we want @@ -352,8 +359,10 @@ def handle_command(fireball_message): set_pm_preference(fireball_message.requestor_id, fireball_message.setting) if fireball_message.setting: msg = "PM Preference: On" + msg = "Receive PM's: On" else: msg = "PM Preference: Off" + msg = "Receive PM's: Off\n*Warning:* _Future messages that were sent only to you will look like this. This type of response does not typically persist between slack sessions._" send_message_to = fireball_message.requestor_id_only else: From 53224158159919c73dc8befc20b483076773376e Mon Sep 17 00:00:00 2001 From: JacobJ Date: Wed, 15 Nov 2017 20:57:15 -0600 Subject: [PATCH 08/10] small fixes/issue prevention --- hey_fireball.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/hey_fireball.py b/hey_fireball.py index df44f24..84d9b39 100644 --- a/hey_fireball.py +++ b/hey_fireball.py @@ -80,7 +80,8 @@ def __init__(self, msg): self.target_name = self.target_id self.command = self._extract_command() self.count = self._extract_count() - self.setting = self._extract_setting() + self.setting = self._extract_setting() # Find on/off or assume toggle + self.ts = msg['ts'] # Store the thread_ts def __str__(self): return str(vars(self)) @@ -154,18 +155,21 @@ def _extract_count(self): def _extract_setting(self): if self.bot_is_first: idx = 2 + curPref = get_pm_preference(self.requestor_id) try: self.parts[idx] except IndexError: # Act as a toggle - if get_pm_preference(self.requestor_id): + if curPref: return 0 else: return 1 - if self.parts[idx].lower() == 'on': + if self.parts[idx].lower() == 'on' and not curPref: return 1 - elif self.parts[idx].lower() == 'off': + elif self.parts[idx].lower() == 'off' and curPref: return 0 + else: + pass ''' # Use the following to catch and handle missing methods/properties as we want @@ -351,11 +355,10 @@ def handle_command(fireball_message): elif fireball_message.command == 'setpm': set_pm_preference(fireball_message.requestor_id, fireball_message.setting) if fireball_message.setting: - msg = "PM Preference: On" + msg = "Receive PM's: On" else: - msg = "PM Preference: Off" + msg = "Receive PM's: Off\n*Warning:* _Future messages that were sent only to you will look like this. This type of response does not typically persist between slack sessions._" send_message_to = fireball_message.requestor_id_only - else: # Message was not valid, so msg = f'{fireball_message.requestor_id}: I do not understand your message. Try again!' @@ -371,6 +374,11 @@ def handle_command(fireball_message): slack_client.api_call("chat.postEphemeral", channel=fireball_message.channel, text=msg, user=fireball_message.target_id_only, attachments=attach) + # elif (fireball_message.command == 'fullboard' or + # fireball_message.command == 'leaderboard'): + # slack_client.api_call("chat.postMessage", channel=send_message_to, + # text=msg, as_user=True, attachments=attach, + # thread_ts=fireball_message.ts) else: slack_client.api_call("chat.postMessage", channel=send_message_to, text=msg, as_user=True, attachments=attach) From 8f529b6e0346aad07e202f1dc927cc90fe75a659 Mon Sep 17 00:00:00 2001 From: JacobJ Date: Thu, 16 Nov 2017 08:19:09 -0600 Subject: [PATCH 09/10] _extract_setting - remove try/except clause Checking for length of self.parts to decide if it should act as a toggle. Instead of a try/except for an IndexError --- hey_fireball.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/hey_fireball.py b/hey_fireball.py index 84d9b39..bfef974 100644 --- a/hey_fireball.py +++ b/hey_fireball.py @@ -156,10 +156,8 @@ def _extract_setting(self): if self.bot_is_first: idx = 2 curPref = get_pm_preference(self.requestor_id) - try: - self.parts[idx] - except IndexError: - # Act as a toggle + if len(self.parts) < 3: + #Act as a toggle if curPref: return 0 else: From b701eb69e171fb52726717c8eb48230301afcbd2 Mon Sep 17 00:00:00 2001 From: JacobJ Date: Thu, 16 Nov 2017 08:34:49 -0600 Subject: [PATCH 10/10] Removing Local Experiment Testing the possibility of having the bot reply to user's request for a leaderboard/fullboard. Bots do not appear to be capable of posting code snippets. So this is an alternative way of 'collapsing' the leaderboard --- hey_fireball.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/hey_fireball.py b/hey_fireball.py index bfef974..c95539b 100644 --- a/hey_fireball.py +++ b/hey_fireball.py @@ -372,11 +372,6 @@ def handle_command(fireball_message): slack_client.api_call("chat.postEphemeral", channel=fireball_message.channel, text=msg, user=fireball_message.target_id_only, attachments=attach) - # elif (fireball_message.command == 'fullboard' or - # fireball_message.command == 'leaderboard'): - # slack_client.api_call("chat.postMessage", channel=send_message_to, - # text=msg, as_user=True, attachments=attach, - # thread_ts=fireball_message.ts) else: slack_client.api_call("chat.postMessage", channel=send_message_to, text=msg, as_user=True, attachments=attach)