def main(self): """ Pulls stock data for the ticker symbols from a json file and pull data from quandl, preprocesses the data and then build different supervised learning machine learning models and predicts future stock price. :return: None """ logger.info( "------------------Started Stock Price Prediction-----------------" ) # Create instances of all the classes used for stock prediction get_data = GetData(api_key=sys.argv[1]) # Number of dates/data points into the future for which the stock price is to be predicted as a percentage of the # number of dates/data points for which historical data which is already available future_prediction_pcnt = 1 preprocess_data = PreprocessData( future_prediction_pcnt=future_prediction_pcnt) build_models = BuildModels() forecast_prices = Predictions() # Get data from quandl. df = get_data.get_stock_data(update_data=False) # Preprocess data preprocessed_data_dict, original_df_dict = preprocess_data.preprocess_data( df, get_data.stock_ticker_list) models_list = [ "Linear Regression", "Decision Tree Regressor", "Random Forest Regressor" ] # Build models models_dict, model_scores_dict = build_models.build_models( models_list, preprocessed_data_dict, force_build=False) # Predict future stock prices forecast_df_dict = forecast_prices.make_predictions( models_dict, preprocessed_data_dict, original_df_dict) self.plot_forecast(forecast_df_dict, original_df_dict, future_prediction_pcnt)
def __init__(self): self.window = Tk() self.window.protocol("WM_DELETE_WINDOW", self.finishApp) self.window.title('Herramienta de prueba para la predicción') ico = PhotoImage(file=os.path.abspath('.') + '/Activos/term.png') self.window.call('wm', 'iconphoto', self.window._w, ico) self.window.config(bg=mainColor) self.window.attributes("-zoomed", False) self.window.minsize(width=300, height=300) #Peso 100% en self.window self.window.grid_rowconfigure(0, weight=1) self.window.grid_columnconfigure(0, weight=1) #Ventana principal fija sin ajuste de tamaño self.window.resizable(False, False) #Centrar ventana en función de las dimendsiones del contenedor de inicio self.center(450, 490) self.view = Window(self.window) self.functions = Functions() self.infoW = Info(self.view.dataContainer) self.genDataW = GenData(self.view.dataContainer) self.predW = Predictions(self.view.dataContainer) self.graphsW = Graphs(self.view.dataContainer) #Botones de las acciones del usuario self.view.buttonDisconnect.bind("<Button>", self.disconnect) self.view.buttonToBD.bind("<Button>", self.changeToConnect) self.view.buttonConnect.bind("<Button>", self.checkConnection) self.view.infoButton.bind( "<Button>", lambda event: self.switchSelector( self.view.predButton, self.view.graphButton, self.view. genDataButton, self.view.infoButton, 1)) self.view.genDataButton.bind( "<Button>", lambda event: self.switchSelector( self.view.infoButton, self.view.predButton, self.view. graphButton, self.view.genDataButton, 2)) self.view.predButton.bind( "<Button>", lambda event: self.switchSelector( self.view.infoButton, self.view.graphButton, self.view. genDataButton, self.view.predButton, 3)) self.view.graphButton.bind( "<Button>", lambda event: self.switchSelector( self.view.infoButton, self.view.genDataButton, self.view. predButton, self.view.graphButton, 4)) self.genDataW.buttonFilterData.bind("<Button>", lambda event: self.getNewGenData()) self.graphsW.buttonGetGraph.bind("<Button>", lambda event: self.getGraphsData()) self.predW.buttonForecast.bind("<Button>", self.getForecast) self.predW.butHistForecast.bind("<Button>", self.getHistForecast) #Ventana principal actualizaciones self.window.mainloop()
def __init__(self, wotv_bot_config: WotvBotConfig): self.wotv_bot_config = wotv_bot_config # Set this to true in an integration test to allow a local filesystem path to be used in a Discord # message as the source of the image to be processed by OCR for Vision Card text extraction. For # obvious security reasons, this is false by default. self.INTEG_TEST_LOCAL_FILESYSTEM_READ_FOR_VISION_CARD = False # Set the static instance of the bot to this instance. WotvBot.__staticInstance = self self.whimsy_shop_nrg_reminder_delay_ms: int = 30 * 60 * 1000 # 30 minutes self.whimsy_shop_spawn_reminder_delay_ms: int = 60 * 60 * 1000 # 60 minutes self.predictions = Predictions('predictions.txt') self.predictions.refreshPredictions() self.last_status = None # Last status set
def makeAllPredictions(self, algorithms, outputFolder, topPredictionsFolder=None): self.updateGraphByOneNavigation() if self.navPath.getLength() < 2: raise RuntimeError( 'makeAllPredictions: Not enough navigations to run predictive algorithms' ) # Build the output data structure results = {} for algorithm in algorithms: results[algorithm.name] = Predictions(algorithm.name, outputFolder, algorithm.fileName, algorithm.includeTop, topPredictionsFolder) totalPredictions = self.navPath.getLength() - 1 for _ in range(1, totalPredictions + 1): self.updateGraphByOneNavigation() print 'Making predictions for navigation #' + str( self.navNumber) + ' of ' + str(totalPredictions) for algorithm in algorithms: results[algorithm.name].addPrediction( self.__makePrediction(algorithm)) print 'Done making predictions.' print self.graph.printEntireGraphStats() return results
def get_model_predictions(self, model, model_name, data): tl_file_name = self.get_tl_file_name(model_name) if os.path.isfile(tl_file_name): model.load_weights(tl_file_name) probs = model.predict([data, data, data]) preds = probs.argmax(axis=-1) return Predictions(model_name, preds)
def getPredictions(self): url = "http://webservices.nextbus.com/service/publicXMLFeed?command=predictionsForMultiStops&a={}".format(self.agency.tag) for stop in self.stopList: url += "&stops={}|{}".format(self.tag,stop.tag) response = requests.get(url) body = ElementTree.fromstring(response.content) predictions = [] for predictionsTag in body.findall('predictions'): predictions.append(Predictions(predictionsTag).toDict()) return predictions
def handlePredictions(previous_rating, new_rating, channel_name, client_id, access_token, twitch_id): print("previous_rating: " + str(previous_rating)) print("new_rating: " + str(new_rating)) predicter = Predictions(channel_name, twitch_id, client_id, access_token) # Rating gain if new_rating > previous_rating: predicter.run(True) # Rating loss elif new_rating < previous_rating: predicter.run(False) # No Rating Change else: pass # no way to determine if a 0 mmr game occured
class Aplicacion(): #Inicialización def __init__(self): self.window = Tk() self.window.protocol("WM_DELETE_WINDOW", self.finishApp) self.window.title('Herramienta de prueba para la predicción') ico = PhotoImage(file=os.path.abspath('.') + '/Activos/term.png') self.window.call('wm', 'iconphoto', self.window._w, ico) self.window.config(bg=mainColor) self.window.attributes("-zoomed", False) self.window.minsize(width=300, height=300) #Peso 100% en self.window self.window.grid_rowconfigure(0, weight=1) self.window.grid_columnconfigure(0, weight=1) #Ventana principal fija sin ajuste de tamaño self.window.resizable(False, False) #Centrar ventana en función de las dimendsiones del contenedor de inicio self.center(450, 490) self.view = Window(self.window) self.functions = Functions() self.infoW = Info(self.view.dataContainer) self.genDataW = GenData(self.view.dataContainer) self.predW = Predictions(self.view.dataContainer) self.graphsW = Graphs(self.view.dataContainer) #Botones de las acciones del usuario self.view.buttonDisconnect.bind("<Button>", self.disconnect) self.view.buttonToBD.bind("<Button>", self.changeToConnect) self.view.buttonConnect.bind("<Button>", self.checkConnection) self.view.infoButton.bind( "<Button>", lambda event: self.switchSelector( self.view.predButton, self.view.graphButton, self.view. genDataButton, self.view.infoButton, 1)) self.view.genDataButton.bind( "<Button>", lambda event: self.switchSelector( self.view.infoButton, self.view.predButton, self.view. graphButton, self.view.genDataButton, 2)) self.view.predButton.bind( "<Button>", lambda event: self.switchSelector( self.view.infoButton, self.view.graphButton, self.view. genDataButton, self.view.predButton, 3)) self.view.graphButton.bind( "<Button>", lambda event: self.switchSelector( self.view.infoButton, self.view.genDataButton, self.view. predButton, self.view.graphButton, 4)) self.genDataW.buttonFilterData.bind("<Button>", lambda event: self.getNewGenData()) self.graphsW.buttonGetGraph.bind("<Button>", lambda event: self.getGraphsData()) self.predW.buttonForecast.bind("<Button>", self.getForecast) self.predW.butHistForecast.bind("<Button>", self.getHistForecast) #Ventana principal actualizaciones self.window.mainloop() #Centra la ventana def center(self, width, height): w = self.window.winfo_screenwidth() h = self.window.winfo_screenheight() x = int((w - width) / 2) y = int((h - height) / 2) self.window.geometry('+{}+{}'.format(x, y)) #Función para comprobar a conexión con la base de datos con los parametros introducidos def checkConnection(self, event): global connectionData connectionData['hostDir'] = self.view.dir.get() connectionData['serverUser'] = self.view.user.get() connectionData['userPassword'] = self.view.password.get() connectionData['port'] = self.view.portEntry.get() connectionData['databaseName'] = self.view.dataB.get() if (connectionData['hostDir'] == None or connectionData['hostDir'] == ''): resultConnection = 'Debe indicar la dirección del servidor' elif (connectionData['serverUser'] == None or connectionData['serverUser'] == ''): resultConnection = 'Debe introducir un usuario' elif (connectionData['userPassword'] == None): resultConnection = 'Debe introducir una contraseña' elif (connectionData['port'] == None or connectionData['port'] == ''): resultConnection = 'Debe indicar el puerto de conexión' elif (connectionData['databaseName'] == None or connectionData['databaseName'] == ''): resultConnection = 'Debe indicar la base de datos a usar' else: global bd bd = BD(connectionData) resultConnection = bd.connect() self.view.setConnectionResult(self.window, resultConnection) if (resultConnection == True): #Se obtienen los nombres de las tablas a mostrar estData = bd.getTableNames(connectionData['databaseName']) #Se olvida el frame de conexion self.view.firstContainer.grid_forget() #Ventana principal fija sin ajuste de tamaño self.window.resizable(True, True) #Se establece el frame princial y sus widgets self.view.mainView(self.window) self.genDataW.complete(estData) self.predW.complete(estData) self.graphsW.complete(estData) self.getNewGenData() self.getGraphsData() self.genDataW.hideGenData() self.predW.hidePredictions() self.graphsW.hideGraphs() self.infoW.showInfo() #Pasar a la vista de conexxion def changeToConnect(self, event): #Olvidar el grid de start container y eliminarlo self.view.startContainer.grid_forget() self.view.startContainer.destroy() self.view.firstContainer.grid(row=0, column=0) #Función para elegir pestaña en la ventana principal def switchSelector(self, button1, button2, button3, button4, number): if (number == 1): self.genDataW.hideGenData() self.predW.hidePredictions() self.graphsW.hideGraphs() self.infoW.showInfo() elif (number == 2): self.infoW.hideInfo() self.predW.hidePredictions() self.graphsW.hideGraphs() self.genDataW.showGenData() elif (number == 3): self.infoW.hideInfo() self.genDataW.hideGenData() self.graphsW.hideGraphs() self.predW.showPredictions() else: self.infoW.hideInfo() self.genDataW.hideGenData() self.predW.hidePredictions() self.graphsW.showGraphs() button1.config(bg=mainColor, activebackground=thirdColor) button2.config(bg=mainColor, activebackground=thirdColor) button3.config(bg=mainColor, activebackground=thirdColor) button4.config(bg=secondaryColor, activebackground=secondaryColor) #Filtra los datos que se tienen que mostrar en la pestaña de datos generales def getNewGenData(self): self.genDataW.textResult.set('') if (self.genDataW.desdeE.get() != '' and self.genDataW.hastaE.get() != ''): try: d = datetime(int(self.genDataW.desdeE.get()[0:4]), int(self.genDataW.desdeE.get()[5:7]), int(self.genDataW.desdeE.get()[8:10])) h = datetime(int(self.genDataW.hastaE.get()[0:4]), int(self.genDataW.hastaE.get()[5:7]), int(self.genDataW.hastaE.get()[8:10]), 23, 00, 00) if (d <= h): result = 1 result = self.functions.getGenData(bd, d, h) else: self.genDataW.textResult.set( 'La primera fecha debe ser menor que la segunda') return except: self.genDataW.textResult.set( 'El formato de las fechas es incorrecto') return elif (self.genDataW.desdeE.get() == '' and self.genDataW.hastaE.get() != ''): try: h = datetime(int(self.genDataW.hastaE.get()[0:4]), int(self.genDataW.hastaE.get()[5:7]), int(self.genDataW.hastaE.get()[8:10]), 23, 00, 00) result = self.functions.getGenData(bd, self.genDataW.desdeE.get(), h) except: self.genDataW.textResult.set( 'El formato de las fechas es incorrecto') return elif (self.genDataW.desdeE.get() != '' and self.genDataW.hastaE.get() == ''): try: d = datetime(int(self.genDataW.desdeE.get()[0:4]), int(self.genDataW.desdeE.get()[5:7]), int(self.genDataW.desdeE.get()[8:10])) result = self.functions.getGenData(bd, d, self.genDataW.hastaE.get()) except: self.genDataW.textResult.set( 'El formato de las fechas es incorrecto') return else: result = self.functions.getGenData(bd, self.genDataW.desdeE.get(), self.genDataW.hastaE.get()) if (result == None): self.genDataW.textResult.set( 'No hay datos para las fechas seleccionadas') else: self.genDataW.start.set(result[1]) self.genDataW.finish.set(result[2]) self.genDataW.changeTableValues(result[0]) #Filtra los datos de los gráficos def getGraphsData(self): self.graphsW.textGraphResult.set('') estacion = self.graphsW.localization.get() meteoData = self.graphsW.atr.get() if (estacion == ''): self.graphsW.textGraphResult.set('Debe elegir una localización') elif (meteoData == ''): self.graphsW.textGraphResult.set('Debe elegir una variable') else: if (self.graphsW.dgEntry.get() != '' and self.graphsW.hgEntry.get() != ''): try: d = datetime(int(self.graphsW.dgEntry.get()[0:4]), int(self.graphsW.dgEntry.get()[5:7]), int(self.graphsW.dgEntry.get()[8:10])) h = datetime(int(self.graphsW.hgEntry.get()[0:4]), int(self.graphsW.hgEntry.get()[5:7]), int(self.graphsW.hgEntry.get()[8:10]), 23, 00, 00) if (d <= h): result = 1 result = bd.selectFromTable(estacion, d, h) else: self.graphsW.textGraphResult.set( 'La primera fecha debe ser menor que la segunda') return except: self.graphsW.textGraphResult.set( 'El formato de las fechas es incorrecto') return elif (self.graphsW.dgEntry.get() == '' and self.graphsW.hgEntry.get() != ''): try: h = datetime(int(self.graphsW.hgEntry.get()[0:4]), int(self.graphsW.hgEntry.get()[5:7]), int(self.graphsW.hgEntry.get()[8:10]), 23, 00, 00) result = bd.selectFromTable(estacion, self.graphsW.dgEntry.get(), h) except: self.graphsW.textGraphResult.set( 'El formato de las fechas es incorrecto') return elif (self.graphsW.dgEntry.get() != '' and self.graphsW.hgEntry.get() == ''): try: d = datetime(int(self.graphsW.dgEntry.get()[0:4]), int(self.graphsW.dgEntry.get()[5:7]), int(self.graphsW.dgEntry.get()[8:10])) result = bd.selectFromTable(estacion, d, self.graphsW.hgEntry.get()) except: self.graphsW.textGraphResult.set( 'El formato de las fechas es incorrecto') return else: result = bd.selectFromTable(estacion, self.graphsW.dgEntry.get(), self.graphsW.hgEntry.get()) if (len(result) == 0): self.graphsW.textGraphResult.set( 'No hay datos para las fechas seleccionadas') else: figure = self.functions.graphData( self.graphsW.localization.get(), self.graphsW.atr.get(), result) self.graphsW.newGraph(figure) #Función al pulsar el botón de predecir def getForecast(self, event): self.predW.butHistForecast.grid_forget() self.predW.resultPredContainer.grid_columnconfigure(2, weight=0) self.predW.predErr.set('') self.predW.predResult.set('') self.predW.textForecast.set('') atr = [] vars = [] if (self.predW.loc.get() == 'None'): self.predW.predErr.set( 'Debe seleccionar una localización para poder completar la predicción' ) return elif (self.predW.t.get() == 0 and self.predW.w.get() == 0 and self.predW.p.get() == 0 and self.predW.h.get() == 0 and self.predW.d.get() == 0): self.predW.predErr.set( 'Debe seleccionar al menos una variable para poder completar la predicción' ) return elif (self.predW.mod.get() == 'None'): self.predW.predErr.set( 'Debe seleccionar una modelo de predicción para poder completar la predicción' ) return else: if (self.predW.t.get() == 1): atr.append('temperatura') if (self.predW.w.get() == 1): atr.append('viento') if (self.predW.d.get() == 1): atr.append('direccion') if (self.predW.p.get() == 1): atr.append('presion') if (self.predW.h.get() == 1): atr.append('humedad') if (self.predW.mod.get() == 'K-NN'): vars.append(self.predW.neighbors.get()) elif (self.predW.mod.get() == 'Red neuronal'): vars.append(self.predW.layers.get()) vars.append(self.predW.neurons1.get()) vars.append(self.predW.neurons2.get()) vars.append(self.predW.neurons3.get()) vars.append(self.predW.itersRedNeu.get()) vars.append(self.predW.learnRate.get()) elif (self.predW.mod.get() == 'Árbol de decisión'): if (self.predW.maxDepth.get() != 0): vars.append(self.predW.maxDepth.get()) else: vars.append(None) else: None res = bd.selectDataForecast( self.predW.loc.get(), self.predW.iter.get() + int(self.predW.dayRef.get()), False) result = self.functions.valoresTempMaximas(res) pred = self.functions.dataForecast(result, self.predW.mod.get(), atr, vars, int(self.predW.dayRef.get()), self.predW.iter.get()) self.predW.showPrediction(pred) #Mostrar gráfico con historico de predicciones def getHistForecast(self, event): self.predW.predErr.set('') atr = [] vars = [] if (self.predW.loc.get() == 'None'): self.predW.predErr.set( 'Debe seleccionar una localización para poder completar la predicción' ) return elif (self.predW.t.get() == 0 and self.predW.w.get() == 0 and self.predW.p.get() == 0 and self.predW.h.get() == 0 and self.predW.d.get() == 0): self.predW.predErr.set( 'Debe seleccionar al menos una variable para poder completar la predicción' ) return elif (self.predW.mod.get() == 'None'): self.predW.predErr.set( 'Debe seleccionar una modelo de predicción para poder completar la predicción' ) return else: if (self.predW.t.get() == 1): atr.append('temperatura') if (self.predW.w.get() == 1): atr.append('viento') if (self.predW.d.get() == 1): atr.append('direccion') if (self.predW.p.get() == 1): atr.append('presion') if (self.predW.h.get() == 1): atr.append('humedad') if (self.predW.mod.get() == 'K-NN'): vars.append(self.predW.neighbors.get()) elif (self.predW.mod.get() == 'Red neuronal'): vars.append(self.predW.layers.get()) vars.append(self.predW.neurons1.get()) vars.append(self.predW.neurons2.get()) vars.append(self.predW.neurons3.get()) vars.append(self.predW.itersRedNeu.get()) vars.append(self.predW.learnRate.get()) elif (self.predW.mod.get() == 'Árbol de decisión'): if (self.predW.maxDepth.get() != 0): vars.append(self.predW.maxDepth.get()) else: vars.append(None) else: None res = bd.selectDataForecast( self.predW.loc.get(), self.predW.iter.get() + int(self.predW.dayRef.get()), True) result = self.functions.valoresTempMaximas(res) figure = self.functions.AllForecast(result, self.predW.mod.get(), atr, vars, int(self.predW.dayRef.get()), self.predW.iter.get()) self.predW.showHist(figure) #Función para descconectar de la base de datos def disconnect(self, event): global bd self.view.execute = False try: bd.close() except: None #Olvidar self.mainContainer self.view.mainContainer.grid_forget() #Atributos de la ventana self.window.attributes("-zoomed", False) self.window.geometry('450x490') self.center(450, 490) self.window.resizable(False, False) #Cargar connection container self.view.firstContainer.grid(row=0, column=0) self.view.connectionView(self.window) def finishApp(self): global bd self.view.execute = False try: bd.close() except: None try: self.view.t1.join(0) self.view.t2.join(1) except: None sys.exit(0)
class WotvBot: """An instance of the bot, configured to manage specific spreadsheets and using Discord and Google credentials.""" # The static instance of the bot, not for general consumption. __staticInstance: WotvBot = None def __init__(self, wotv_bot_config: WotvBotConfig): self.wotv_bot_config = wotv_bot_config # Set this to true in an integration test to allow a local filesystem path to be used in a Discord # message as the source of the image to be processed by OCR for Vision Card text extraction. For # obvious security reasons, this is false by default. self.INTEG_TEST_LOCAL_FILESYSTEM_READ_FOR_VISION_CARD = False # Set the static instance of the bot to this instance. WotvBot.__staticInstance = self self.whimsy_shop_nrg_reminder_delay_ms: int = 30 * 60 * 1000 # 30 minutes self.whimsy_shop_spawn_reminder_delay_ms: int = 60 * 60 * 1000 # 60 minutes self.predictions = Predictions('predictions.txt') self.predictions.refreshPredictions() self.last_status = None # Last status set @staticmethod def getStaticInstance(): """Returns an unsafe static reference to the "current" bot, if there is one. In reality this is just the most recently-created bot. Use with extreme caution. This is primarily intended for internal use cases where a static method is required, such as the callback for a "apscheduler"-module task such as a reminder that is being invoked asynchronously and potentially across different instances of the bot process where the specific instance of the bot is irrelevant. """ return WotvBot.__staticInstance async def handleMessage(self, message: discord.Message): """Process the request and produce a response.""" # Bail out early if anything looks insane. if message.author == self.wotv_bot_config.discord_client.user: return (None, None) if not message.content: return (None, None) if not message.content.startswith('!'): return (None, None) for ignore_pattern in WotvBotConstants.ALL_IGNORE_PATTERNS: if ignore_pattern.match(message.content): return (None, None) # Set up the context used in handling every possible command. # TODO: Clean up these fields that are not part of the CommandContextInfo object. from_name = message.author.display_name from_id = message.author.id from_discrim = message.author.discriminator context = CommandContextInfo() context.from_discrim = from_discrim context.from_id = from_id context.from_name = from_name context.original_message = message # TODO: Hold these references longer after cleaning up the rest of the code, in an application context. esper_resonance_manager = EsperResonanceManager( self.wotv_bot_config.esper_resonance_spreadsheet_id, self.wotv_bot_config.sandbox_esper_resonance_spreadsheet_id, self.wotv_bot_config.access_control_spreadsheet_id, self.wotv_bot_config.spreadsheet_app) vision_card_manager = VisionCardManager( self.wotv_bot_config.vision_card_spreadsheet_id, self.wotv_bot_config.access_control_spreadsheet_id, self.wotv_bot_config.spreadsheet_app) # To support multi-line commands, we only match the command itself against the first line. first_line_lower = message.content.splitlines()[0].lower() match = WotvBotConstants.RES_FETCH_SELF_PATTERN.match(first_line_lower) if match: return self.handleTargetedResonanceLookupForSelf( context.shallowCopy().withMatch( match).withEsperResonanceManager(esper_resonance_manager)) match = WotvBotConstants.RES_LIST_SELF_PATTERN.match(first_line_lower) if match: return self.handleGeneralResonanceLookupForSelf( context.shallowCopy().withMatch( match).withEsperResonanceManager(esper_resonance_manager)) match = WotvBotConstants.RES_FETCH_OTHER_PATTERN.match( first_line_lower) if match: return self.handleTargetedResonanceLookupForOtherUser( context.shallowCopy().withMatch( match).withEsperResonanceManager(esper_resonance_manager)) match = WotvBotConstants.RES_SET_PATTERN.match(first_line_lower) if match: return self.handleResonanceSet(context.shallowCopy().withMatch( match).withEsperResonanceManager(esper_resonance_manager)) if WotvBotConstants.VISION_CARD_SET_PATTERN.match(first_line_lower): return await self.handleVisionCardSet( context.shallowCopy().withVisionCardManager( vision_card_manager)) match = WotvBotConstants.VISION_CARD_FETCH_BY_NAME_PATTERN.match( first_line_lower) if match: return await self.handleVisionCardFetchByName( context.shallowCopy().withMatch(match).withVisionCardManager( vision_card_manager)) match = WotvBotConstants.VISION_CARD_ABILITY_SEARCH.match( first_line_lower) if match: return await self.handleVisionCardAbilitySearch( context.shallowCopy().withMatch(match).withVisionCardManager( vision_card_manager)) if WotvBotConstants.VISION_CARD_DEBUG_PATTERN.match(first_line_lower): return await self.handleVisionCardDebug( context.shallowCopy().withVisionCardManager( vision_card_manager)) match = WotvBotConstants.FIND_SKILLS_BY_NAME_PATTERN.match( first_line_lower) if match: return await self.handleFindSkillsByName( context.shallowCopy().withMatch(match)) match = WotvBotConstants.FIND_SKILLS_BY_DESCRIPTION_PATTERN.match( first_line_lower) if match: return await self.handleFindSkillsByDescription( context.shallowCopy().withMatch(match)) match = WotvBotConstants.RICH_UNIT_SEARCH_PATTERN.match( first_line_lower) if match: return await self.handleRichUnitSearch( context.shallowCopy().withMatch(match)) match = WotvBotConstants.WHIMSY_REMINDER_PATTERN.match( first_line_lower) if match: return await self.handleWhimsyReminder( context.shallowCopy().withMatch(match)) match = WotvBotConstants.ROLLDICE_PATTERN.match(first_line_lower) if match: return await self.handleRoll(context.shallowCopy().withMatch(match) ) # Predictions match = WotvBotConstants.PREDICTION_PATTERN_1.match(first_line_lower) if match: return await self.handlePrediction( context.shallowCopy().withMatch(match)) match = WotvBotConstants.PREDICTION_PATTERN_2.match(first_line_lower) if match: return await self.handlePrediction( context.shallowCopy().withMatch(match)) match = WotvBotConstants.PREDICTION_PATTERN_3.match(first_line_lower) if match: return await self.handlePrediction( context.shallowCopy().withMatch(match)) match = WotvBotConstants.PREDICTION_PATTERN_4.match(first_line_lower) if match: return await self.handlePrediction( context.shallowCopy().withMatch(match)) match = WotvBotConstants.DOUBLE_DROP_RATES_SCHEDULE_PATTERN_1.match( first_line_lower) if match: return await self.handleSchedule( context.shallowCopy().withMatch(match)) match = WotvBotConstants.DOUBLE_DROP_RATES_SCHEDULE_PATTERN_2.match( first_line_lower) if match: return await self.handleMats(context.shallowCopy().withMatch(match) ) match = WotvBotConstants.DAILY_REMINDERS.match(first_line_lower) if match: return await self.handleDailyReminders( context.shallowCopy().withMatch(match)) # Hidden utility command to look up the snowflake ID of your own user. This isn't secret or insecure, but it's also not common, so it isn't listed. if first_line_lower.startswith('!whoami'): return self.handleWhoAmI(context) # Hidden utility command to look up the snowflake ID of a member. This isn't secret or insecure, but it's also not common, so it isn't listed. match = WotvBotConstants.WHOIS_PATTERN.match(first_line_lower) if match: return await self.handleWhoIs( context.shallowCopy().withMatch(match)) if WotvBotConstants.ADMIN_ADD_ESPER_PATTERN.match( first_line_lower ) or WotvBotConstants.SANDBOX_ADMIN_ADD_ESPER_PATTERN.match( message.content): return self.handleAdminAddEsper( context.shallowCopy().withEsperResonanceManager( esper_resonance_manager)) if WotvBotConstants.ADMIN_ADD_UNIT_PATTERN.match( first_line_lower ) or WotvBotConstants.SANDBOX_ADMIN_ADD_UNIT_PATTERN.match( message.content): return self.handleAdminAddUnit( context.shallowCopy().withEsperResonanceManager( esper_resonance_manager)) if WotvBotConstants.ADMIN_ADD_VC_PATTERN.match(first_line_lower): return self.handleAdminAddVisionCard( context.shallowCopy().withVisionCardManager( vision_card_manager)) if WotvBotConstants.ADMIN_ADD_USER_PATTERN.match(first_line_lower): return self.handleAdminAddUser( context.shallowCopy().withEsperResonanceManager( esper_resonance_manager).withVisionCardManager( vision_card_manager)) if first_line_lower.startswith('!resonance'): responseText = '<@{0}>: Invalid !resonance command. Use !help for more information.'.format( from_id) return (responseText, None) if first_line_lower.startswith('!help'): responseText = WotvBotConstants.HELP.format( self.wotv_bot_config.esper_resonance_spreadsheet_id, self.wotv_bot_config.vision_card_spreadsheet_id) return (responseText, None) return ('<@{0}>: Invalid or unknown command. Use !help to see all supported commands and !admin-help to see special admin commands. '\ 'Please do this via a direct message to the bot, to avoid spamming the channel.'.format(from_id), None) def handleTargetedResonanceLookupForSelf( self, context: CommandContextInfo) -> (str, str): """Handle !res command for self-lookup of a specific (unit, esper) tuple.""" unit_name = context.command_match.group(1).strip() esper_name = context.command_match.group(2).strip() print( 'resonance fetch from user %s#%s, for user %s, for unit %s, for esper %s' % (context.from_name, context.from_discrim, context.from_name, unit_name, esper_name)) resonance, pretty_unit_name, pretty_esper_name = context.esper_resonance_manager.readResonance( None, context.from_id, unit_name, esper_name) responseText = '<@{0}>: {1}/{2} has resonance {3}'.format( context.from_id, pretty_unit_name, pretty_esper_name, resonance) return (responseText, None) def handleTargetedResonanceLookupForOtherUser( self, context: CommandContextInfo) -> (str, str): """Handle !res command for lookup of a specific (unit, esper) tuple for a different user.""" target_user_name = context.command_match.group(1).strip() unit_name = context.command_match.group(2).strip() esper_name = context.command_match.group(3).strip() print( 'resonance fetch from user %s#%s, for user %s, for unit %s, for esper %s' % (context.from_name, context.from_discrim, target_user_name, unit_name, esper_name)) resonance, pretty_unit_name, pretty_esper_name = context.esper_resonance_manager.readResonance( target_user_name, None, unit_name, esper_name) responseText = '<@{0}>: for user {1}, {2}/{3} has resonance {4}'.format( context.from_id, target_user_name, pretty_unit_name, pretty_esper_name, resonance) return (responseText, None) def handleGeneralResonanceLookupForSelf( self, context: CommandContextInfo) -> (str, str): """Handle !res command for self-lookup of all resonance for a given unit or esper.""" target_name = context.command_match.group('target_name').strip() print('resonance list fetch from user %s#%s, for target %s' % (context.from_name, context.from_discrim, target_name)) pretty_name, resonance_listing = context.esper_resonance_manager.readResonanceList( None, context.from_id, target_name) responseText = '<@{0}>: resonance listing for {1}:\n{2}'.format( context.from_id, pretty_name, resonance_listing) return (responseText, None) def handleResonanceSet(self, context: CommandContextInfo) -> (str, str): """Handle !res-set command to set resonance for a specific unit and esper tuple.""" unit_name = context.command_match.group('unit').strip() esper_name = context.command_match.group('esper').strip() resonance_numeric_string = context.command_match.group( 'resonance_level').strip() priority = None if context.command_match.group('priority'): priority = context.command_match.group('priority').strip() comment = None if context.command_match.group('comment'): comment = context.command_match.group('comment').strip() print( 'resonance set from user %s#%s, for unit %s, for esper %s, to resonance %s, with priority %s, comment %s' % (context.from_name, context.from_discrim, unit_name, esper_name, resonance_numeric_string, priority, comment)) old_resonance, new_resonance, pretty_unit_name, pretty_esper_name = context.esper_resonance_manager.setResonance( context.from_id, unit_name, esper_name, resonance_numeric_string, priority, comment) responseText = '<@{0}>: {1}/{2} resonance has been set to {3} (was: {4})'.format( context.from_id, pretty_unit_name, pretty_esper_name, new_resonance, old_resonance) if (resonance_numeric_string and int(resonance_numeric_string) == 10): # reaction = '\U0001F4AA' # CLDR: flexed biceps reaction = '\U0001F3C6' # CLDR: trophy else: reaction = '\U00002705' # CLDR: check mark button return (responseText, reaction) def handleWhoAmI(self, context: CommandContextInfo) -> (str, str): """Handle !whoami command to fetch your own snowflake ID.""" responseText = '<@{id}>: Your snowflake ID is {id}'.format( id=context.from_id) return (responseText, None) async def handleWhoIs(self, context: CommandContextInfo) -> (str, str): """Handle !whois command to fetch the snowflake ID for a given user.""" original_match = WotvBotConstants.WHOIS_PATTERN.match( context.original_message.content) # Fetch original-case name target_member_name = original_match.group('server_handle').strip() # As of December 2020, possibly earlier, the following line no longer works: # members = context.original_message.guild.members # Instead have to fetch the list from the server, and enable the "SERVER MEMBERS INTENT" permission in the bot admin page on Discord. members = await context.original_message.guild.fetch_members( limit=1000).flatten() for member in members: if member.name == target_member_name: responseText = '<@{0}>: the snowflake ID for {1} is {2}'.format( context.from_id, target_member_name, member.id) return (responseText, None) responseText = '<@{0}>: no such member {1}'.format( context.from_id, target_member_name) return (responseText, None) def handleAdminAddEsper(self, context: CommandContextInfo) -> (str, str): """Handle !admin-add-esper and !sandbox-admin-add-esper commands to add a new esper to the resonance tracker.""" sandbox = True match = WotvBotConstants.ADMIN_ADD_ESPER_PATTERN.match( context.original_message.content) if match: sandbox = False else: match = WotvBotConstants.SANDBOX_ADMIN_ADD_ESPER_PATTERN.match( context.original_message.content) esper_name = match.group('name').strip() esper_url = match.group('url').strip() left_or_right_of = match.group('left_or_right_of').strip() column = match.group('column').strip() print( 'esper add (sandbox mode={6}) from user {0}#{1}, for esper {2}, url {3}, position {4}, column {5}' .format(context.from_name, context.from_discrim, esper_name, esper_url, left_or_right_of, column, sandbox)) context.esper_resonance_manager.addEsperColumn(context.from_id, esper_name, esper_url, left_or_right_of, column, sandbox) responseText = '<@{0}>: Added esper {1}!'.format( context.from_id, esper_name) return (responseText, None) def handleAdminAddUnit(self, context: CommandContextInfo) -> (str, str): """Handle !admin-add-unit and !sandbox-admin-add-unit commands to add a new unit to the resonance tracker.""" sandbox = True match = WotvBotConstants.ADMIN_ADD_UNIT_PATTERN.match( context.original_message.content) if match: sandbox = False else: match = WotvBotConstants.SANDBOX_ADMIN_ADD_UNIT_PATTERN.match( context.original_message.content) unit_name = match.group('name').strip() unit_url = match.group('url').strip() above_or_below = match.group('above_or_below').strip() row1Based = match.group('row1Based').strip() print( 'unit add (sandbox mode={6}) from user {0}#{1}, for unit {2}, url {3}, position {4}, row {5}' .format(context.from_name, context.from_discrim, unit_name, unit_url, above_or_below, row1Based, sandbox)) context.esper_resonance_manager.addUnitRow(context.from_id, unit_name, unit_url, above_or_below, row1Based, sandbox) responseText = '<@{0}>: Added unit {1}!'.format( context.from_id, unit_name) return (responseText, None) def handleAdminAddVisionCard(self, context: CommandContextInfo) -> (str, str): """Handle !admin-add-vc command to add a new vision card.""" match = WotvBotConstants.ADMIN_ADD_VC_PATTERN.match( context.original_message.content) card_name = match.group('name').strip() card_url = match.group('url').strip() above_or_below = match.group('above_or_below').strip() row1Based = match.group('row1Based').strip() print( 'vc add from user {0}#{1}, for card {2}, url {3}, position {4}, row {5}' .format(context.from_name, context.from_discrim, card_name, card_url, above_or_below, row1Based)) context.vision_card_manager.addVisionCardRow(context.from_id, card_name, card_url, above_or_below, row1Based) responseText = '<@{0}>: Added card {1}!'.format( context.from_id, card_name) return (responseText, None) def handleAdminAddUser(self, context: CommandContextInfo) -> (str, str): """Handle !admin-add-user command to add a new unit to the resonance tracker and the administrative spreadsheet.""" if not AdminUtils.isAdmin( self.wotv_bot_config.spreadsheet_app, self.wotv_bot_config.access_control_spreadsheet_id, context.from_id): raise ExposableException( 'You do not have permission to add a user.') match = WotvBotConstants.ADMIN_ADD_USER_PATTERN.match( context.original_message.content) snowflake_id = match.group('snowflake_id').strip() nickname = match.group('nickname').strip() user_type = match.group('user_type').strip().lower() is_admin = False if user_type == 'admin': is_admin = True print( 'user add from user {0}#{1}, for snowflake_id {2}, nickname {3}, is_admin {4}' .format(context.from_name, context.from_discrim, snowflake_id, nickname, is_admin)) AdminUtils.addUser(self.wotv_bot_config.spreadsheet_app, self.wotv_bot_config.access_control_spreadsheet_id, nickname, snowflake_id, is_admin) context.esper_resonance_manager.addUser(nickname) context.vision_card_manager.addUser(nickname) responseText = '<@{0}>: Added user {1}!'.format( context.from_id, nickname) return (responseText, None) async def handleVisionCardDebug(self, context: CommandContextInfo) -> (str, str): """Handle !xocr and !xocr-debug commands to perform OCR on a Vision Card.""" return await self.handleVisionCardSet(context, is_debug=True) async def handleVisionCardSet(self, context: CommandContextInfo, is_debug: bool = False) -> (str, str): """Handle !vc-set""" # Try to extract text from a vision card screenshot that is sent as an attachment to this message. url = context.original_message.attachments[0].url print('Vision Card OCR request from user %s#%s, for url %s' % (context.from_name, context.from_discrim, url)) screenshot = None if self.INTEG_TEST_LOCAL_FILESYSTEM_READ_FOR_VISION_CARD: screenshot = VisionCardOcrUtils.loadScreenshotFromFilesystem(url) else: screenshot = VisionCardOcrUtils.downloadScreenshotFromUrl(url) vision_card = VisionCardOcrUtils.extractVisionCardFromScreenshot( screenshot, is_debug) if is_debug: combined_image = VisionCardOcrUtils.mergeDebugImages(vision_card) buffer = io.BytesIO() combined_image.save(buffer, format='PNG') buffer.seek(0) temp_file = discord.File(buffer, filename='Intermediate OCR Debug.png') await context.original_message.channel.send( 'Intermediate OCR Debug. Raw info text:\n```{0}```\nRaw stats text: ```{1}```' .format(vision_card.info_debug_raw_text, vision_card.stats_debug_raw_text), file=temp_file) # Print errors to the console, but do not return them as we cannot guarantee that there is no sensitive # information in here, such as possible library exceptions, i/o exceptions, etceteras. if vision_card.error_messages is not None and len( vision_card.error_messages) > 0: print('errors found during vision card conversion: ' + str(vision_card.error_messages)) reaction = None if vision_card.successfully_extracted is True: responseText = '<@{0}>: {1}'.format(context.from_id, vision_card.prettyPrint()) if not is_debug: context.vision_card_manager.setVisionCard( context.from_id, vision_card) reaction = '\U00002705' # CLDR: check mark button else: responseText = '<@{0}>: Vision card extraction has failed. You may try again with !vc-debug for a clue about what has gone wrong'.format( context.from_id) return (responseText, reaction) async def handleVisionCardFetchByName( self, context: CommandContextInfo) -> (str, str): """Handle !vc command for self-lookup of a given vision card by name""" target_name = context.command_match.group('target_name').strip() print('vision card fetch from user %s#%s, for target %s' % (context.from_name, context.from_discrim, target_name)) vision_card = context.vision_card_manager.readVisionCardByName( None, context.from_id, target_name) responseText = '<@{0}>: Vision Card:\n{1}'.format( context.from_id, str(vision_card.prettyPrint())) return (responseText, None) async def handleVisionCardAbilitySearch( self, context: CommandContextInfo) -> (str, str): """Handle !vc-ability command for self-lookup of a given vision card by party/bestowed ability fuzzy-match""" search_text = context.command_match.group('search_text').strip() print('vision card ability search from user %s#%s, for text %s' % (context.from_name, context.from_discrim, search_text)) vision_cards = context.vision_card_manager.searchVisionCardsByAbility( None, context.from_id, search_text) if len(vision_cards) == 0: responseText = '<@{0}>: No vision cards matched the ability search.'.format( context.from_id) return (responseText, None) responseText = '<@{0}>: Matching Vision Cards:\n'.format( context.from_id) for vision_card in vision_cards: responseText += ' ' + vision_card.Name + '\n' responseText += ' Party Ability: ' + vision_card.PartyAbility + '\n' for bestowed_effect in vision_card.BestowedEffects: responseText += ' Bestowed Effect: ' + bestowed_effect + '\n' return (responseText, None) @staticmethod def rarityAndElementParenthetical(unit: WotvUnit) -> str: """Generate a parenthetical string with the unit's rarity and element(s)""" text = '(' + str(unit.rarity) + ' rarity, ' if not unit.elements: return text + 'no element)' text += unit.elements[0] if len(unit.elements) > 1: for element in unit.elements[1:]: text += '/' + str(element) text += ' element' if len(unit.elements) > 1: text += 's' return text + ')' def prettyPrintUnitSkillSearchResult(self, result: UnitSkillSearchResult): """Print a useful, human-readable description of the skill match including the unit name, element, rarity, the skill name, and how the skill is unlocked.""" if result.is_master_ability: return 'Master ability for ' + result.unit.name + ' ' + WotvBot.rarityAndElementParenthetical( result.unit) + ': ' + result.skill.description if result.is_limit_burst: return 'Limit burst (' + result.skill.name + ') for ' + result.unit.name + ' ' + WotvBot.rarityAndElementParenthetical( result.unit) + ': ' + result.skill.description text = 'Skill "' + result.skill.name + '" learned by ' + result.unit.name text += ' ' + WotvBot.rarityAndElementParenthetical(result.unit) text += ' with job ' + result.board_skill.unlocked_by_job.name + ' at job level ' + str( result.board_skill.unlocked_by_job_level) text += ': ' + result.skill.description return text def prettyPrintUnitJobSearchResult(self, result: UnitJobSearchResult): """Print a useful, human-readable description of the job match including the unit name, element, rarity, and job name.""" text = 'Job "' + result.job.name + '" learned by ' + result.unit.name text += ' ' + WotvBot.rarityAndElementParenthetical(result.unit) return text def prettyPrintUnitSearchResult(self, result: UnitSearchResult): """Print a useful, human-readable description of any search result, as appropriate to the type.""" if hasattr(result, 'is_master_ability'): return self.prettyPrintUnitSkillSearchResult(result) elif hasattr(result, 'job'): return self.prettyPrintUnitJobSearchResult(result) else: return result.unit.name + ' ' + WotvBot.rarityAndElementParenthetical( result.unit) @staticmethod def getExtraCommandLines(context: CommandContextInfo): """Extract all extra non-empty lines from a command and return them as a list.""" lines = context.original_message.content.splitlines() extra_lines = [] if len(lines) > 1: for line in lines[1:]: line = line.strip() if line: extra_lines.append(line) return extra_lines # Deprecated - Use rich unit search instead, e.g. "!unit-search skill-name <search_text>" async def handleFindSkillsByName( self, context: CommandContextInfo) -> (str, str): """Handle !skills-by-name command""" search_text = context.command_match.group('search_text').strip() print('skills-by-name search from user %s#%s, for text %s' % (context.from_name, context.from_discrim, search_text)) refinements = WotvBot.getExtraCommandLines(context) if len(refinements) > 0: print(' refinements: ' + str(refinements)) results = DataFileSearchUtils.richUnitSearch( self.wotv_bot_config.data_files, 'skill-name', search_text, refinements) if len(results) == 0: responseText = '<@{0}>: No skills matched the search.'.format( context.from_id) return (responseText, None) responseText = '<@{0}>: Matching Skills:\n'.format(context.from_id) results = sorted(results, key=lambda one_result: one_result.unit.name) truncated = False if len(results) > 25: results = results[:25] truncated = True for result in results: responseText += self.prettyPrintUnitSearchResult(result) + '\n' if truncated: responseText += 'Results truncated because there were too many.' return (responseText.strip(), None) # Deprecated - Use rich unit search instead, e.g. "!unit-search skill-desc <search_text>" async def handleFindSkillsByDescription( self, context: CommandContextInfo) -> (str, str): """Handle !skills-by-desc command""" search_text = context.command_match.group('search_text').strip() print('skills-by-description search from user %s#%s, for text %s' % (context.from_name, context.from_discrim, search_text)) refinements = WotvBot.getExtraCommandLines(context) if len(refinements) > 0: print(' refinements: ' + str(refinements)) results = DataFileSearchUtils.richUnitSearch( self.wotv_bot_config.data_files, 'skill-desc', search_text, refinements) if len(results) == 0: responseText = '<@{0}>: No skills matched the search.'.format( context.from_id) return (responseText, None) responseText = '<@{0}>: Matching Skills:\n'.format(context.from_id) results = sorted(results, key=lambda one_result: one_result.unit.name) truncated = False if len(results) > 25: results = results[:25] truncated = True for result in results: responseText += self.prettyPrintUnitSearchResult(result) + '\n' if truncated: responseText += 'Results truncated because there were too many.' return (responseText.strip(), None) async def handleRichUnitSearch(self, context: CommandContextInfo) -> (str, str): """Handle !unit-search command""" search_type = context.command_match.group('search_type').strip() search_text = None if search_type != 'all': search_text = context.command_match.group('search_text').strip() print('unit search from user %s#%s, type %s, text %s' % (context.from_name, context.from_discrim, search_type, search_text)) refinements = WotvBot.getExtraCommandLines(context) if len(refinements) > 0: print(' refinements: ' + str(refinements)) results = DataFileSearchUtils.richUnitSearch( self.wotv_bot_config.data_files, search_type, search_text, refinements) if len(results) == 0: responseText = '<@{0}>: No units matched the search.'.format( context.from_id) return (responseText, None) responseText = '<@{0}>: Results:\n'.format(context.from_id) results = sorted(results, key=lambda one_result: one_result.unit.name) truncated = False if len(results) > 25: results = results[:25] truncated = True for result in results: responseText += self.prettyPrintUnitSearchResult(result) + '\n' if truncated: responseText += 'Results truncated because there were too many.' return (responseText.strip(), None) @staticmethod async def whimsyShopNrgReminderCallback(target_channel_id: str, from_id: str): """Handles a reminder callback for a whimsy shop nrg reminder.""" discord_client: discord.Client = WotvBot.getStaticInstance( ).wotv_bot_config.discord_client text_channel: discord.TextChannel = discord_client.get_channel( target_channel_id) #discord_client.loop.create_task(text_channel.send(content = '<@{0}>: This is your requested whimsy shop reminder: NRG spent will now start counting towards the next Whimsy Shop.'.format(from_id))) await text_channel.send( content= '<@{0}>: This is your requested whimsy shop reminder: NRG spent will now start counting towards the next Whimsy Shop.' .format(from_id)) @staticmethod async def whimsyShopSpawnReminderCallback(target_channel_id: str, from_id: str): """Handles a reminder callback for a whimsy shop spawn reminder.""" discord_client: discord.Client = WotvBot.getStaticInstance( ).wotv_bot_config.discord_client text_channel: discord.TextChannel = discord_client.get_channel( target_channel_id) #discord_client.loop.create_task(text_channel.send(content = '<@{0}>: This is your requested whimsy shop reminder: The Whimsy Shop is ready to spawn again.'.format(from_id))) await text_channel.send( content= '<@{0}>: This is your requested whimsy shop reminder: The Whimsy Shop is ready to spawn again.' .format(from_id)) async def handleWhimsyReminder(self, context: CommandContextInfo) -> (str, str): """Handle !whimsy command for a whimsy reminder""" reminders = self.wotv_bot_config.reminders # Shorthand owner_id = str(context.from_id) # Shorthand command = '<none>' if context.command_match.group('command'): command = context.command_match.group('command').strip() print('Whimsy reminder request from user %s#%s, command %s' % (context.from_name, context.from_discrim, command)) responseText = '<@{0}>: Unknown/unsupported !whimsy command. Use !help for for more information.'.format( context.from_id) # Default behavior - be smart. If the user has got a reminder set, don't overwrite it unless they pass set-reminder as the command. # If they do not have a reminder set, go ahead and set it now. append_overwrite_reminder_message = False # Whether or not to add some reminder text to the message if command == '<none>': # Check if an existing reminder is set. If so prompt to overwrite... if reminders.hasPendingWhimsyNrgReminder( owner_id) or reminders.hasPendingWhimsySpawnReminder( owner_id): command = 'when' append_overwrite_reminder_message = True # Remind the user how to overwrite the current timer. else: command = 'set-reminder' # Assume the user wants to set a reminder. if command == 'set-reminder': append_existing_canceled_message = reminders.hasPendingWhimsyNrgReminder( owner_id) or reminders.hasPendingWhimsySpawnReminder(owner_id) nrg_callback: callable = WotvBot.whimsyShopNrgReminderCallback nrg_params = [context.original_message.channel.id, owner_id] spawn_callback: callable = WotvBot.whimsyShopSpawnReminderCallback spawn_params = nrg_params reminders.addWhimsyReminder( context.from_name, owner_id, nrg_callback, nrg_params, spawn_callback, spawn_params, self.whimsy_shop_nrg_reminder_delay_ms, self.whimsy_shop_spawn_reminder_delay_ms) responseText = '<@{0}>: Your reminder has been set.'.format( context.from_id) if append_existing_canceled_message: responseText += ' Your previous outstanding reminder has been discarded.' elif command == 'when': if reminders.hasPendingWhimsyNrgReminder(owner_id): time_left_minutes = int( reminders.timeTillWhimsyNrgReminder(owner_id) / 60) responseText = '<@{0}>: NRG spent will start counting towards the next Whimsy Shop in about {1} minutes.'.format( owner_id, str(time_left_minutes)) if append_overwrite_reminder_message: responseText += ' To force the timer to reset to 60 minutes *immediately*, use the command "!whimsy set-reminder".' elif reminders.hasPendingWhimsySpawnReminder(owner_id): time_left_minutes = int( reminders.timeTillWhimsySpawnReminder(owner_id) / 60) responseText = '<@{0}>: The Whimsy Shop will be ready to spawn again in about {1} minutes.'.format( owner_id, str(time_left_minutes)) if append_overwrite_reminder_message: responseText += ' To force the timer to reset to 60 minutes *immediately*, use the command "!whimsy set-reminder".' else: responseText = '<@{0}>: You do not currently have a whimsy reminder set.'.format( context.from_id) elif command == 'cancel': reminders.cancelWhimsyReminders(owner_id) responseText = '<@{0}>: Any and all outstanding whimsy reminders have been canceled.'.format( context.from_id) return (responseText, None) async def handleRoll(self, context: CommandContextInfo) -> (str, str): """Handle !roll command to simulate a dice roll.""" spec: DiceSpec = DiceSpec.parse( context.command_match.group('dice_spec')) print('Dice roll request from user %s#%s, spec %s' % (context.from_name, context.from_discrim, str(spec))) if spec.num_dice > 50: responseText = '<@{0}>: Too many dice in !roll command (max 50). Use !help for for more information.'.format( context.from_id) else: results: List[int] = Rolling.rollDice(spec) total = 0 for one_roll in results: total += one_roll responseText = '<@{0}>: Rolled a total of {1}. Dice values were: {2}'.format( context.from_id, str(total), str(results)) return (responseText.strip(), None) async def handlePrediction(self, context: CommandContextInfo) -> (str, str): """Handle !predict/astrologize/divine/foretell (etc) command to make a funny prediction.""" query = context.command_match.group('query') print('Prediction request from user %s#%s, query %s' % (context.from_name, context.from_discrim, str(query))) responseText = '<@{0}>: {1}'.format(context.from_id, self.predictions.predict(query)) return (responseText.strip(), None) async def handleSchedule(self, context: CommandContextInfo) -> (str, str): """Handle a request for the weekly schedule.""" print('Schedule request from user %s#%s' % (context.from_name, context.from_discrim)) responseText = '<@{0}>:\n{1}'.format( context.from_id, WeeklyEventSchedule.getDoubleDropRateSchedule('** >> ', ' << **')) return (responseText.strip(), None) async def handleMats(self, context: CommandContextInfo) -> (str, str): """Handle a request for the current double-drop rate room.""" print('Mats request from user %s#%s' % (context.from_name, context.from_discrim)) responseText = '<@{0}>:\n'.format(context.from_id) responseText += 'Today: ' + WeeklyEventSchedule.getTodaysDoubleDropRateEvents( ) + '\n' responseText += 'Tomorrow: ' + WeeklyEventSchedule.getTomorrowsDoubleDropRateEvents( ) + '\n' responseText += 'For the full schedule, use !schedule.' return (responseText.strip(), None) async def createOrResetPeriodicStatusUpdateCallback(self): """Create or reset the status update callback for the entire bot.""" self.wotv_bot_config.reminders.createOrResetPeriodicStatusUpdateCallback( WotvBot.periodicStatusUpdateCallback) @staticmethod async def periodicStatusUpdateCallback(): """Handles a callback for a periodic status update.""" bot: WotvBot = WotvBot.getStaticInstance() discord_client: discord.Client = bot.wotv_bot_config.discord_client new_status = WeeklyEventSchedule.getTodaysDoubleDropRateEvents() if bot.last_status is None or bot.last_status != new_status: print('Updating bot status to: ' + new_status) # Apparently bots cannot use a custom status so gotta stick with a regular one like "Playing" (Game) await discord_client.change_presence(activity=discord.Game( name=new_status)) bot.last_status = new_status @staticmethod async def dailyReminderCallback(target_channel_id: str, from_id: str, requested_reminders: List[str]): """Handles a reminder callback for daily reminders.""" discord_client: discord.Client = WotvBot.getStaticInstance( ).wotv_bot_config.discord_client text_channel: discord.TextChannel = discord_client.get_channel( target_channel_id) reminder_text = '<@{0}>: This is your requested daily reminder. Cancel daily reminders with "!daily-reminders none" or use "!help".'.format( from_id) if 'mats' in requested_reminders: reminder_text += '\n Today\'s daily double rate drops are: ' + WeeklyEventSchedule.getTodaysDoubleDropRateEvents( ) await text_channel.send(content=reminder_text) async def handleDailyReminders(self, context: CommandContextInfo) -> (str, str): """Handle !daily-reminders command for various daily reminders, such as double-drop-rates""" reminders = self.wotv_bot_config.reminders # Shorthand owner_id = str(context.from_id) # Shorthand reminder_list_str = '<default>' if context.command_match.group('reminder_list'): reminder_list_str = context.command_match.group( 'reminder_list').strip() print('Daily reminders request from user %s#%s, reminder list %s' % (context.from_name, context.from_discrim, reminder_list_str)) responseText = '<@{0}>: Unknown/unsupported !daily-reminders command. Use !help for for more information.'.format( context.from_id) requested_reminders: List[str] = reminder_list_str.split(',') configured_reminders_message = '<@{0}>: Your daily reminders have been configured:'.format( context.from_id) # Default behavior - be smart. If the user has got a reminder set, don't overwrite it unless they pass "none" as the list. if reminder_list_str == '<default>': if reminders.hasDailyReminder(owner_id): responseText = '<@{0}>: You have daily reminders configured. To clear them, use "!daily-reminders none".'.format( context.from_id) else: responseText = '<@{0}>: You do not currently have daily reminders configured. Use !help for more information.'.format( context.from_id) elif reminder_list_str == 'none': reminders.cancelDailyReminder(owner_id) responseText = '<@{0}>: Your daily reminders have been canceled.'.format( context.from_id) else: added_reminders = [] if 'mats' in requested_reminders: configured_reminders_message += '\n daily double-drop rate reminder ("mats")' added_reminders.append('mats') callback: callable = WotvBot.dailyReminderCallback callback_params = [ context.original_message.channel.id, owner_id, added_reminders ] reminders.addDailyReminder(context.from_name, owner_id, callback, callback_params) responseText = configured_reminders_message return (responseText, None)
def __init__(self, league: League, num_epochs: int, learning_rate: float, nn_shape: List[int], season: str, split: float, outfile: str, model_dir: str, features: List[str], cache_dir: str, mode: str = DEFAULT_METHOD, normalize_weights: bool = False, cache_numpy_structures=False, predict_next_season=False, next_season_csv: str = None): """ Parameters ---------- league: League The main League object num_epochs: int How many epochs to run this configuration for. learning_rate: float The weight at which to update weights in TensorFlow nn_shape: list A list of integers corresponding to the NN topology. For example [22, 10] is a 2 layer Network with 22 and 10 nodes in the layers. season: str The season or seasons that we are preforming training/testing on split: float The split at which to divide training/testing data outfile: str A path to a file, where data about each program execution will be dumped, effectivley serving as a history of previous models. model_dir: str A path to a master directory of models. Individual model directories for various configurations will all be placed into this directory features: list A list of features to use as inputs for the NN or SVM. logger: logging A logger for this class mode: str What learning mode we should run the NBAPredictor in for this execution. Options are "DNN" or "SVM" normalize_weights: bool If set to true, a weight column will be added onto the training and testing examples to boost or downplay certain instances cache_numpy_structures: bool If set to True, the NumPy array from this object will be cached as a pickle object so that if the same dataset is used again we do not need to prefrom all the parsing and normalizations. predict_next_season: bool If set to True, this will not test against existing Data, but instead try to predict the upcoming 2019-2020 SEASON NBA next_season_csv: str Path to next seasons CSV file """ self.logger = logging.getLogger( f"NBAPredictor.{self.__class__.__name__}") self.mode = mode self.leauge = league self.num_epochs = num_epochs self.learning_rate = learning_rate self.nn_shape = nn_shape self.model_dir = model_dir self.normalize_weights = normalize_weights self.predict_next_season = predict_next_season if self.mode == "SVM": self.parsed_season = ReadGames( self.leauge, season, split, cache_dir, features, svm_compat=True, normalize_weights=self.normalize_weights, cache=cache_numpy_structures) else: self.parsed_season = ReadGames(self.leauge, season, split, cache_dir, features, svm_compat=False, normalize_weights=self.normalize_weights, cache=cache_numpy_structures) if not self.predict_next_season else \ PredictNextSeason( next_season_csv=next_season_csv, leauge=league, season=season, split=split, cache_dir=cache_dir, features=features, cache=cache_numpy_structures) self.feature_cols = self.create_feature_columns() self.model = self.create_model() if self.mode == "SVM": self.predictions = Predictions(season, num_epochs, nn_shape, self.parsed_season.features, outfile, svm_compat=True) else: self.predictions = Predictions(season, num_epochs, nn_shape, self.parsed_season.features, outfile, svm_compat=False) self.train_input_function = tf.estimator.inputs.numpy_input_fn( x=self.parsed_season.training_features, y=self.parsed_season.training_labels, batch_size=500, num_epochs=None, shuffle=False) self.test_input_function = tf.estimator.inputs.numpy_input_fn( x=self.parsed_season.testing_features, y=self.parsed_season.testing_labels, num_epochs=1, shuffle=False) if self.predict_next_season: self.predict_input_function = tf.estimator.inputs.numpy_input_fn( x=self.parsed_season.next_season_data, num_epochs=1, shuffle=False) if self.mode == "SVM": self.logger.info( f"Running SVC on the {season} NBA Season(s) for " f"{self.num_epochs} epochs with the following input features used: " f"{self.parsed_season.features}") else: self.logger.info( f"Running DNN on the {season} NBA Season(s) for" f" {self.num_epochs} " f"epochs with the following NN shape: {self.nn_shape} and the following input " f"features: " f"{self.parsed_season.features}")
class TensorflowOperations: """ The TensorflowOperations class is a class that NBAPredictor uses as a facade into various Machine Learning libraries such as sklearn and TensorFlow. This module runs our entire training and testing routines before passing off control to the Predictions class to organize results data. Attributes ---------- mode: str What learning mode we should run the NBAPredictor in for this execution. Options are "DNN" or "SVM" league: League The main League object num_epochs: int How many epochs to run this configuration for. learning_rate: float The weight at which to update weights in TensorFlow nn_shape: list A list of integers corresponding to the NN topology. For example [22, 10] is a 2 layer Network with 22 and 10 nodes in the layers. model_dir: str A path to a master directory of models. Individual model directories for various configurations will all be placed into this directory normalize_weights: bool If set to true, a weight column will be added onto the training and testing examples to boost or downplay certain instances parsed_season: ReadGames A ReadGames instance. This object contains all of the training/testing features and labels required to run any of the models in TensorflowOperations. feature_cols: list A list of feature columns for this instance model: Tensorflow DNN Classifier or Scikit SVM model. An ML model configured according to the parameters requested at object initialization. predictions: Predictions A predictions object to use after all training/testing has concluded. The Predictions class is in charge of post-analysis. train_input_function: tf.estimator.inputs.numpy_input_fn An input function to use for training test_input_function: tf.estimator.inputs.numpy_input_fn An input function to use for testing logger: logging A logger for this class Methods ------- create_feature_columns Creates a list of feature columns from the features passed in on initialization run Higher-level function that runs the NBAPredictor for this instance, for the requested number of epochs. "Runs" in this instance refers to training, testing, and evaluating. train In the case of DNN, the model will be trained against the training features in the parsed_season object. For SVM strategy, the model will be fit against the training features. evaluate Used by the TensorFlow DNN in order to evaluate the DNN against the test input function. get_predictions This is the method tasked with testing against the trained samples. For each game in the testing data, make a Home or Away Win/Loss prediction. Predictions are expected to be in the format of a tuple of floats such as [0.45, 0.55], where the first index is the odds the home team wins and the second index is the odds the away team wins. This list of predictions is passed onto the Predictions object for further post-analysis. """ def __init__(self, league: League, num_epochs: int, learning_rate: float, nn_shape: List[int], season: str, split: float, outfile: str, model_dir: str, features: List[str], cache_dir: str, mode: str = DEFAULT_METHOD, normalize_weights: bool = False, cache_numpy_structures=False, predict_next_season=False, next_season_csv: str = None): """ Parameters ---------- league: League The main League object num_epochs: int How many epochs to run this configuration for. learning_rate: float The weight at which to update weights in TensorFlow nn_shape: list A list of integers corresponding to the NN topology. For example [22, 10] is a 2 layer Network with 22 and 10 nodes in the layers. season: str The season or seasons that we are preforming training/testing on split: float The split at which to divide training/testing data outfile: str A path to a file, where data about each program execution will be dumped, effectivley serving as a history of previous models. model_dir: str A path to a master directory of models. Individual model directories for various configurations will all be placed into this directory features: list A list of features to use as inputs for the NN or SVM. logger: logging A logger for this class mode: str What learning mode we should run the NBAPredictor in for this execution. Options are "DNN" or "SVM" normalize_weights: bool If set to true, a weight column will be added onto the training and testing examples to boost or downplay certain instances cache_numpy_structures: bool If set to True, the NumPy array from this object will be cached as a pickle object so that if the same dataset is used again we do not need to prefrom all the parsing and normalizations. predict_next_season: bool If set to True, this will not test against existing Data, but instead try to predict the upcoming 2019-2020 SEASON NBA next_season_csv: str Path to next seasons CSV file """ self.logger = logging.getLogger( f"NBAPredictor.{self.__class__.__name__}") self.mode = mode self.leauge = league self.num_epochs = num_epochs self.learning_rate = learning_rate self.nn_shape = nn_shape self.model_dir = model_dir self.normalize_weights = normalize_weights self.predict_next_season = predict_next_season if self.mode == "SVM": self.parsed_season = ReadGames( self.leauge, season, split, cache_dir, features, svm_compat=True, normalize_weights=self.normalize_weights, cache=cache_numpy_structures) else: self.parsed_season = ReadGames(self.leauge, season, split, cache_dir, features, svm_compat=False, normalize_weights=self.normalize_weights, cache=cache_numpy_structures) if not self.predict_next_season else \ PredictNextSeason( next_season_csv=next_season_csv, leauge=league, season=season, split=split, cache_dir=cache_dir, features=features, cache=cache_numpy_structures) self.feature_cols = self.create_feature_columns() self.model = self.create_model() if self.mode == "SVM": self.predictions = Predictions(season, num_epochs, nn_shape, self.parsed_season.features, outfile, svm_compat=True) else: self.predictions = Predictions(season, num_epochs, nn_shape, self.parsed_season.features, outfile, svm_compat=False) self.train_input_function = tf.estimator.inputs.numpy_input_fn( x=self.parsed_season.training_features, y=self.parsed_season.training_labels, batch_size=500, num_epochs=None, shuffle=False) self.test_input_function = tf.estimator.inputs.numpy_input_fn( x=self.parsed_season.testing_features, y=self.parsed_season.testing_labels, num_epochs=1, shuffle=False) if self.predict_next_season: self.predict_input_function = tf.estimator.inputs.numpy_input_fn( x=self.parsed_season.next_season_data, num_epochs=1, shuffle=False) if self.mode == "SVM": self.logger.info( f"Running SVC on the {season} NBA Season(s) for " f"{self.num_epochs} epochs with the following input features used: " f"{self.parsed_season.features}") else: self.logger.info( f"Running DNN on the {season} NBA Season(s) for" f" {self.num_epochs} " f"epochs with the following NN shape: {self.nn_shape} and the following input " f"features: " f"{self.parsed_season.features}") def create_feature_columns(self) -> List[tf.feature_column.numeric_column]: """ Creates a list of feature columns from the features passed in on initialization Returns ------- list A list of feature columns for each item in the ReadGame object feature attribute """ return [ tf.feature_column.numeric_column(key=item) for item in self.parsed_season.features ] def create_model(self) -> Union[tf.estimator.DNNClassifier, svm.SVC]: if self.mode == "DNN": if self.normalize_weights: return tf.estimator.DNNClassifier( model_dir=self.model_dir, hidden_units=self.nn_shape, feature_columns=self.feature_cols, n_classes=2, label_vocabulary=['H', 'A'], weight_column=tf.feature_column.numeric_column('weight'), optimizer=tf.compat.v1.train.ProximalAdagradOptimizer( learning_rate=self.learning_rate, l1_regularization_strength=0.001)) else: return tf.estimator.DNNClassifier( model_dir=self.model_dir, hidden_units=self.nn_shape, feature_columns=self.feature_cols, n_classes=2, label_vocabulary=['H', 'A'], optimizer=tf.compat.v1.train.ProximalAdagradOptimizer( learning_rate=self.learning_rate, l1_regularization_strength=0.001)) elif self.mode == "SVM": return svm.SVC(kernel='rbf') else: raise RuntimeError( f"{self.mode} is not an implemented or recognized ML strategy in NBAPredictor" ) def run(self) -> None: """ Higher-level function that runs the NBAPredictor for this instance, for the requested number of epochs. "Runs" in this instance refers to training, testing, and evaluating. Returns ------- None """ if self.mode == "DNN": if not self.predict_next_season: for x in range(0, self.num_epochs): self.logger.info(f"Running instance #{x + 1}") self.train() self.evaluate() self.get_predictions() else: self.logger.info(f"Predicting NBA Season for 2019-2020") for x in range(0, self.num_epochs): self.logger.info(f"Running instance #{x + 1}") self.train() acc = self.evaluate() self.logger.info( f"Accuracy: {float(acc['accuracy']) * 100}") playoffs: Playoffs = self.get_predictions() while playoffs.generate_test_data_for_playoffs(): testing_features = playoffs.generate_test_data_for_playoffs( ) self.predict_input_function = tf.estimator.inputs.numpy_input_fn( x=testing_features, num_epochs=1, shuffle=False) predictions = list( self.model.predict( input_fn=self.predict_input_function)) playoffs.record_playoff_results(predictions) playoffs.log_playoff_results() time.sleep(8) elif self.mode == "SVM": for x in range(0, self.num_epochs): self.logger.info(f"Running instance #{x + 1}") self.train() self.get_predictions() else: raise RuntimeError( f"{self.mode} is not an implemented or recognized ML strategy in NBAPredictor" ) self.predictions.analyze_end_performance( ) if not self.predict_next_season else None def train(self) -> None: """ In the case of DNN, the model will be trained against the training features in the parsed_season object. For SVM strategy, the model will be fit against the training features. Returns ------- None """ if self.mode == "DNN": self.model.train(input_fn=self.train_input_function, steps=10) elif self.mode == "SVM": self.model.fit(self.parsed_season.training_features, self.parsed_season.training_labels) else: raise RuntimeError( f"{self.mode} is not an implemented or recognized ML strategy in NBAPredictor" ) def evaluate(self) -> float: """ Used by the TensorFlow DNN in order to evaluate the DNN against the test input function Returns ------- None """ return self.model.evaluate(input_fn=self.test_input_function) def get_predictions(self) -> Union[None, Playoffs]: """ This is the method tasked with testing against the trained samples. For each game in the testing data, make a Home or Away Win/Loss prediction. Predictions are expected to be in the format of a tuple of floats such as [0.45, 0.55], where the first index is the odds the home team wins and the second index is the odds the away team wins. This list of predictions is passed onto the Predictions object for further post-analysis Returns ------- None """ if self.mode == "DNN": if not self.predict_next_season: predictions = list( self.model.predict(input_fn=self.test_input_function)) start_index = self.parsed_season.training_size + 1 predicted_games = [ self.parsed_season.sorted_games[i] for i in range( start_index, len(self.parsed_season.sorted_games)) ] self.predictions.add_dnn_seasonal_prediction_instance( predictions, predicted_games, self.model) else: next_season_predictions = list( self.model.predict(input_fn=self.predict_input_function)) return self.parsed_season.analyze_end_of_season_predictions( next_season_predictions) elif self.mode == "SVM": predictions = self.model.predict( self.parsed_season.testing_features) start_index = self.parsed_season.training_size + 1 predicted_games = [ self.parsed_season.sorted_games[i] for i in range( start_index, len(self.parsed_season.sorted_games)) ] self.predictions.add_svm_seasons_prediction_instance( predictions, predicted_games) else: raise RuntimeError( f"{self.mode} is not an implemented or recognized ML strategy in NBAPredictor" )
from predictions import Predictions from predictions_valuebets import ValueBets from volume import Volume predictions_vanilla = Predictions() value_bets = ValueBets() volume_of_bets = Volume() predictions_vanilla.scrape() value_bets.scrape() volume_of_bets.scrape()