def duckboot(): #{{{ # Get bot token from the env file with open("../.env") as env_file: bot_token = env_file.readline().rstrip().split("=")[1] util.logger.log(DiagMessage("INI0010I", bot_token)) # Create the slack client util.sc = SlackClient(bot_token) util.logger.log(DiagMessage("INI0020I")) # Get bot info bot_str, bot_channels = util.getBotInfo(bot_token) bot_id = util.matchUserId(bot_str) if not bot_id: util.logger.log(DiagMessage("INI0030E",bot_str)) return util.EXIT_CODES["INVALID_BOT_ID"], None util.logger.log(DiagMessage("INI0030I",bot_id)) # Connect to rtm and create bot if successful return_code = connect() if return_code: return return_code, None else: return return_code, Duckbot(bot_id, bot_channels)
def connect(): #{{{ # Connect to the rtm if util.sc.rtm_connect(with_team_state=False): #{{{ event_list = [] # Wait for the connection event while not event_list: event_list = util.sc.rtm_read() event = event_list.pop(0) if event["type"] == "hello": # Connection was good util.logger.log(DiagMessage("INI0040I")) return 0 else: # Error in connection error = event["error"] util.logger.log(DiagMessage("INI0040E",error["msg"])) util.logger.log(DiagMessage("INI0041E",str(error["code"]))) return util.EXIT_CODES["RTM_BAD_CONNECTION"] #}}} # RTM connect failed else: util.logger.log(DiagMessage("INI0042E")) return util.EXIT_CODES["RTM_CONNECT_FAILED"]
def run(duckbot): #{{{ if util.debug: util.logger.log(DiagMessage("INI0050D")) else: util.logger.log(DiagMessage("INI0050I")) running = True return_code = 0 # Keep going until bot signals to stop while running: # {{{ # Pause between reads to reduce the cycles spent spinning # Delay may need to be adjusted if bot feels sluggish to respond time.sleep(util.RTM_READ_DELAY) return_code, event_list = doRead() if event_list and not return_code: # Process all the events returned for event in event_list: return_code = duckbot.handleEvent(event) # Tick bot internal counter duckbot.tick() if return_code: running = False # }}} # Bot signalled to stop, return to mainline return return_code
def doRead(): #{{{ # Attempt to do rtm read, except errors # When new errors are experienced, they will be added specifically try: event_list = util.sc.rtm_read() return 0, event_list except TimeoutError: util.logger.log(DiagMessage("BOT0031E")) return util.EXIT_CODES["RTM_TIMEOUT_ERROR"], None except Exception as err: print("Error: RTM read failed") print(err,file=sys.stderr) util.logger.log(DiagMessage("BOT0030E")) return util.EXIT_CODES["RTM_GENERIC_ERROR"], None
def __init__(self, fn = DEFAULT_FN, log = True): #{{{ self.keep_log = log self.fn = fn self.log_buffer = [] if self.keep_log: self.log(DiagMessage("LOG0000I"))
def tick(self): #{{{ # Tick function is called roughly every second, so the tick count rolls # over about every hour. Roll over point can be changed if time needs to # be tracked longer than an hour at a time. self.ticks = (self.ticks + 1) % self.TICK_ROLLOVER # Flush the buffer every hour or so if there aren't enough messages if self.ticks % util.LOG_TIME == 0: self.logger.log(DiagMessage("LOG0010I"), flush=True) # Save bank data timer if util.bank_file and self.ticks % util.SAVE_STATE_TIME == 0: self.gamble_handler.saveState() # Tick down the global cooldown if self.cooldown_g: self.cooldown_g -= 1 # Regen some bux for the poor people if self.ticks % util.REGEN_TIME == 0: self.gamble_handler.regenBux() # Refresh the free pulls if self.gamble_handler.pull_timer: self.gamble_handler.pull_timer -= 1 else: self.gamble_handler.refreshPulls() # Wonderful day wish if self.wish_timer: self.wish_timer -= 1 else: util.sendMessage(self.WISH_CHANNEL, "Go, have a wonderful day.") self._getWishTime()
def main(): #{{{ initProgram() return_code, duckbot = duckboot() if not return_code: return_code = run(duckbot) util.logger.log(DiagMessage("LOG0011I"), flush=True) return return_code
def handleEvent(self, event_p): #{{{ # print(event_p) # Create standardized event event = Event(event_p) # No event type, get out if event.type == None: return 0 # Message event, pass to message handler elif event.type == "message": #{{{ # Display user and their message if event.text: self.logger.log(DiagMessage("BOT0010U", event.user, event.text)) response = self.msg_handler.act(event) # Send message if one was returned if response: util.sendMessage(event.channel, response, event.user) return 0 # None response signals an update needed elif response == None: util.sendMessage(event.channel, "Shutting down for update. Kweh! :duckbot:") return 2 # Otherwise do nothing else: return 0 #}}} # Bot message event elif event.type == "bot_message": #{{{ self.bot_handler.checkBotId(event.user) # Do something sassy with the bot if not self.cooldown_g: self.cooldown_g = 120 self.bot_handler.act(event) return 0 #}}} # Update event, respond based on the event subtype elif event.type == "update": #{{{ # channel_purpose and channel_joined require channel list update if (event.subtype == "channel_purpose" or event.subtype == "channel_joined"): self._channelListUpdate(event) return 0 #}}} # Unhandled event type else: # Don't do anything right now return 0
def __init__(self, bot_id, bot_channels): #{{{ # Param fields self.id = bot_id self.channels = bot_channels self.debug = util.debug self.logger = util.logger self.ticks = 0 self.cooldown_g = 0 self._getWishTime() self.logger.log(DiagMessage("BOT0000I")) # Create command handlers self.roll_handler = RollHandler() util.logger.log(DiagMessage("BOT0001D", "Roll")) if util.debug else None self.help_handler = HelpHandler(bot_id) util.logger.log(DiagMessage("BOT0001D", "Help")) if util.debug else None self.gamble_handler = GambleHandler(bot_channels) util.logger.log(DiagMessage("BOT0001D", "Gamble")) if util.debug else None # Create event handlers self.msg_handler = MessageHandler(bot_id, gamble_handler=self.gamble_handler, help_handler=self.help_handler, roll_handler=self.roll_handler) self.logger.log(DiagMessage("BOT0001D", "Message")) if self.debug else None self.bot_handler = BotHandler() self.logger.log(DiagMessage("BOT0001D", "Bot")) if self.debug else None self.logger.log(DiagMessage("BOT0002I"))
def initProgram(): #{{{ # Change context directory to the running one os.chdir(os.path.dirname(os.path.realpath(__file__))) # Construct command line parser and get arguements cl_parser = argparse.ArgumentParser(description='Start up Duckbot') cl_parser.add_argument('--debug', action='store_true') cl_parser.add_argument('--nolog', dest='log', action='store_false', default=True) cl_parser.add_argument('--nobnk', dest='bnk', action='store_false', default=True) args = cl_parser.parse_args() util.debug = args.debug util.bank_file = args.bnk # Start the logger with logging mode util.logger = Logger(log=args.log) util.logger.log(DiagMessage("INI0000I"))
def act(self, event): #{{{ u_parms = "" # Split text into command word and params command, o_parms = self._getCommand(event.text) # Save old params for nicer messages if needed if o_parms: u_parms = [str.upper(val) for val in o_parms] # Log command being processed if util.debug and command: util.logger.log( DiagMessage("BOT0020D", util.COMMANDS.inverse[command][0])) # HI command if command == util.COMMANDS["HI"]: return self.DEFAULT_RESPONSE # UPDATE command elif command == util.COMMANDS["UPDATE"]: return None # HELP command elif command == util.COMMANDS["HELP"]: return self.help_handler.act(u_parms) # ROLL command elif command == util.COMMANDS["ROLL"]: #{{{ return_code, rolls = self.roll_handler.roll(u_parms) # Regular dice roll if return_code == 0: #{{{ # Grab what will be added to the end of the message tail = rolls.pop() output = "You rolled: " # Join all the values if there's more than one if len(rolls) > 1: output += ", ".join(map( str, rolls)) + "\nYour total: " + str(tail) # Otherwise grab the one and slap the tail on else: output += str(rolls[0]) + " " + tail return output # }}} # Character roll elif return_code == 1: #{{{ output = "" stats = [] # Go through each set of rolls, drop the lowest and total for group in rolls: output += "\n\nYou rolled: " + ", ".join(map(str, group)) output += "\nDropping " + str(min(group)) + ", " group.remove(min(group)) stat = sum(group) stats.append(stat) output += "Total: " + str(stat) output += "\n\nYour stats are: " + ", ".join(map(str, stats)) return output #}}} elif return_code == -1: return o_parms[0] + " is not a valid roll." else: return "Can't roll without parameters, kweh! :duck:" #}}} # COIN command elif command == util.COMMANDS["COIN"]: return "You got: " + self.roll_handler.coinRoll() # 8BALL command elif command == util.COMMANDS["EIGHTBALL"]: return self.roll_handler.eightballRoll() # FACTOID command elif command == util.COMMANDS["FACTOID"]: return self.roll_handler.factoidRoll() # PICKIT command elif command == util.COMMANDS["PICKIT"]: #{{{ return_code, response = self.roll_handler.pickitRoll(o_parms) # Number of choices out of range if return_code == 1: return ("Must pick between " + str(min(response)) + " " "and " + str(max(response)) + " things") # Parsing error elif return_code == 2: return "Unmatched quotes! Kweh :duck:" else: return "I choose: " + response #}}} # JOIN command elif command == util.COMMANDS["JOIN"]: return self.gamble_handler.join(event.user, event.channel) # CHECKBUX command elif command == util.COMMANDS["CHECKBUX"]: target = u_parms[0] if u_parms else None return self.gamble_handler.checkbux(event.user, target) # BET command elif command == util.COMMANDS["BET"]: return self.gamble_handler.bet(event.user, event.channel, o_parms) # PULL command elif command == util.COMMANDS["PULL"]: amount = u_parms[0] if u_parms else None return self.gamble_handler.pull(event.user, event.channel, amount) # CHECKPOOL command elif command == util.COMMANDS["CHECKPOOL"]: target = u_parms[0] if u_parms else None return self.gamble_handler.checkPool(event.user, target) # No command or unrecognized, either way I don't care else: return ""