Game Adapter Class: Allow superusers to moderate bots.
ADD
zulip_bots/zulip_bots/bots/connect_four/connect_four.conf
+[connect_four]
+superusers = ["user@example.com"]

MODIFY
zulip_bots/zulip_bots/bots/connect_four/connect_four.py
zulip_bots/zulip_bots/bots/connect_four/connect_four.py
@@ -45,7 +45,7 @@ class ConnectFourBotHandler(GameAdapter):
def __init__(self):
game_name = 'Connect Four'
bot_name = 'connect_four'
- move_help_message = '* To make your move during a game, type\n' + \
+ move_help_message = '* To make your move during a game, type\n' \
'```move <column-number>```'
move_regex = 'move (\d)$'
model = ConnectFourModel

MODIFY
zulip_bots/zulip_bots/bots/connect_four/doc.md
zulip_bots/zulip_bots/bots/connect_four/doc.md
@@ -25,8 +25,12 @@ can only run a single game at a time*
## Setup
-The Connect Four Bot does not require a config file or API key.
-It can be used without setup.
+To set moderators for the bot, modify the connect_four.conf
+file as shown:
+
+superusers = ["user@example.com", "user@example2.com", ...]
+
+Moderators can run ```force reset``` in case any user abuse the bot
## Usage
@@ -61,7 +65,7 @@ another user.
player to decline an invitation to play Connect Four against
another user.
-7. ```withdraw invitation``` : a command that can only be run by the
+7. ```cancel game``` : a command that can only be run by the
inviter to withdraw their invitation to play. Especially
useful if a player does not respond to an invitation for a
long period of time.
@@ -75,3 +79,7 @@ the user to confirm they wish to forfeit the game.
10. ```confirm quit``` : causes the user that runs this command
to forfeit the game.
+
+11. ```force reset``` : a command that can only be run by the bot
+owner and moderators (see 'Usage' for specifying). Destroys any
+game currently being run if users are abusing the bot

MODIFY
zulip_bots/zulip_bots/bots/connect_four/game_adapter.py
zulip_bots/zulip_bots/bots/connect_four/game_adapter.py
-# @TODO: place bot owner name in config file, allow bot owner to run special commands
-
import re
+import json
from copy import deepcopy
-# @TODO: allow superusers
class InputVerification(object):
- def __init__(self, move_regex):
+ def __init__(self, move_regex, superusers):
self.move_regex = move_regex
self.verified_commands = {
'waiting': ['start game with computer', 'start game with \w+@\w+\.\w+'],
@@ -13,17 +11,16 @@ class InputVerification(object):
'playing': [[move_regex, 'quit', 'confirm quit'], ['quit', 'confirm quit']]
}
self.all_valid_commands = ['help', 'status', 'start game with computer', 'start game with \w+@\w+\.\w+',
- 'withdraw invitation', 'accept', 'decline', self.move_regex, 'quit', 'confirm quit']
+ 'withdraw invitation', 'accept', 'decline', self.move_regex, 'quit', 'confirm quit', 'force reset']
+ self.superusers = superusers
verified_users = []
-
-
def permission_lacking_message(self, command):
return 'Sorry, but you can\'t run the command ```' + command + '```'
def update_commands(self, turn):
- self.verified_commands['playing'][-1 * turn + 1].remove(self.move_regex)
+ self.verified_commands['playing'] = [['quit', 'confirm quit'], ['quit', 'confirm quit']]
self.verified_commands['playing'][turn].append(self.move_regex)
def reset_commands(self):
@@ -50,6 +47,9 @@ class InputVerification(object):
return self.regex_match_in_array(command_array, command)
+ def verify_superuser(self, user):
+ return user in self.superusers
+
class StateManager(object):
def __init__(self, main_bot_handler):
self.users = None
@@ -331,11 +331,9 @@ class GameAdapter(object):
self.move_help_message = move_help_message
self.model = model
self.gameMessageHandler = gameMessageHandler()
- self.inputVerification = InputVerification(move_regex)
+ self.inputVerification = InputVerification(move_regex, [])
def get_stored_data(self):
- # @TODO: remove this comment when you create super users
- # return self.data # Uncomment and rerun bot to reset data if users are abusing the bot
return self.bot_handler.storage.get(self.bot_name)
def update_data(self):
@@ -343,13 +341,17 @@ class GameAdapter(object):
if 'users' in self.data:
self.inputVerification.verified_users = self.data['users']
+ else:
+ self.inputVerification.verified_users = []
if self.state == 'inviting':
self.invitationHandler = InvitationHandler(self)
self.gameHandler = GameHandler(self, self.data['game_type'], self.model())
elif self.state == 'playing':
- self.gameHandler = GameHandler(self, self.data['game_type'], self.model(), board = self.data['board'], turn = self.data['turn'], )
+ self.gameHandler = GameHandler(self, self.data['game_type'], self.model(),
+ board = self.data['board'], turn = self.data['turn'])
+ self.inputVerification.update_commands(self.data['turn'])
def put_stored_data(self):
self.data = {}
@@ -470,11 +472,26 @@ class GameAdapter(object):
'''
def initialize(self, bot_handler):
+ self.config_info = bot_handler.get_config_info('connect_four')
+ if self.config_info:
+ self.inputVerification.superusers = json.loads(self.config_info['superusers'])
self.gameCreator = GameCreator(self)
self.inputVerification.reset_commands()
+
if not bot_handler.storage.contains(self.bot_name):
bot_handler.storage.put(self.bot_name, self.data)
+ def force_reset(self, sender):
+ for user in self.inputVerification.verified_users:
+ self.send_message(user, 'A bot moderator determined you were abusing the bot, and quit your game.'
+ ' Please make sure you finish all your games in a timely fashion.')
+
+ self.send_message(sender, 'The game has been force reset')
+
+ self.data = data = {'state': 'waiting'}
+ self.update_data()
+ self.put_stored_data()
+
def handle_message(self, message, bot_handler):
self.bot_handler = bot_handler
@@ -489,7 +506,10 @@ class GameAdapter(object):
'Type ```help``` to see a full list of commands.')
return
- # Messages that can be sent regardless of state or user
+ elif self.inputVerification.verify_superuser(sender) and content.lower() == 'force reset':
+ self.force_reset(sender)
+ return
+
elif content.lower() == 'help' or content == '':
self.send_message(sender, self.help_message())
return

MODIFY
zulip_bots/zulip_bots/bots/connect_four/test_connect_four.py
zulip_bots/zulip_bots/bots/connect_four/test_connect_four.py
@@ -149,16 +149,19 @@ class TestConnectFourBot(BotTestCase):
def test_quit(self):
self.verify_response('quit', 'Are you sure you want to quit? You will forfeit the game!\n'
'Type ```confirm quit``` to forfeit.', 0, data=self.start_two_player_data)
-
- def test_confirm_quit(self):
self.verify_response('confirm quit', '**You have forfeit the game**\nSorry, but you lost :cry:', 0, data=self.start_two_player_data)
+ def test_force_reset(self):
+ with self.mock_config_info({'superusers': '["foo@example.com"]'}):
+ self.verify_response('force reset', 'The game has been force reset', 1, data=self.start_one_player_data)
+
def test_privilege_check(self):
self.verify_response('move 4', 'Sorry, but you can\'t run the command ```move 4```', 0, data=self.inviting_two_player_data)
self.verify_response('start game with computer', 'Sorry, but other users are already using the bot.'
'Type ```status``` to see the current status of the bot.', 0, data=self.inviting_two_player_data, user = 'foo3@example.com')
self.verify_response('quit', 'Sorry, but you can\'t run the command ```quit```', 0)
self.verify_response('accept', 'Sorry, but you can\'t run the command ```accept```', 0, data=self.end_two_player_data)
+ self.verify_response('force reset', 'Sorry, but you can\'t run the command ```force reset```', 0)
def test_connect_four_logic(self):
def confirmAvailableMoves(good_moves, bad_moves, board):

Game Adapter Class: Allow superusers to moderate bots.