From 24072d61d5619ca13094a03488867a106e1060a7 Mon Sep 17 00:00:00 2001 From: C'tri Date: Tue, 9 Mar 2021 12:26:40 +0000 Subject: [PATCH 01/19] initial rework - creation of statusMessageClass --- bot.py | 7 ++-- handlers.py | 14 ++++++-- messageHandlers.js | 31 ----------------- requirements.txt | 2 ++ statusMessage.py | 85 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 102 insertions(+), 37 deletions(-) delete mode 100644 messageHandlers.js create mode 100644 requirements.txt create mode 100644 statusMessage.py diff --git a/bot.py b/bot.py index 8dc4760..08b5676 100644 --- a/bot.py +++ b/bot.py @@ -45,6 +45,7 @@ async def on_ready(): print('------') -with open("auth.json",'r') as json_file: - config = json.load(json_file) -bot.run(config['token']) +if __name__ == "__main__": + with open("auth.json",'r') as json_file: + config = json.load(json_file) + bot.run(config['token']) diff --git a/handlers.py b/handlers.py index edc3801..64679e9 100644 --- a/handlers.py +++ b/handlers.py @@ -3,6 +3,7 @@ import platform import math import random +from statusMessage import * from SQLconnector import * async def reactEyes(message): @@ -46,11 +47,18 @@ async def cmdGeneral(message, pieces, bot): async def cmdStatus(message): - + report = statusMessage(channel=message.channel) + report.checkServices() + await report.sendOrUpdate() + return + mesesge ="""**Information Requested: monitored system status.** + Beginning report. + + """ embed = discord.Embed() embed.title = "System status" - embed.add_field(name = "Bot Status", value = "`Active` ✅", inline=True) - embed.add_field(name = "Bot Home", value = '`%s`' % platform.node(), inline=True) + embed.add_field(name = "Service", value = "> 00719274602\nDatabase", inline=True) + embed.add_field(name = "Status", value = '`✅ Active`\t%s\n' % platform.node(), inline=True) try: conn = connectToSource() diff --git a/messageHandlers.js b/messageHandlers.js deleted file mode 100644 index 945de04..0000000 --- a/messageHandlers.js +++ /dev/null @@ -1,31 +0,0 @@ -const messageHandlers = {} - - -messageHandlers.shortName = function(bot, channelID, userID, cmd) -{ - bot.sendMessage({to:channelID, message : 'Additionally, please be advised that my full designation is 00718274602. Please do not shorten it.'}); -} -messageHandlers.help = function(bot, channelID, userID, cmd) -{ - bot.sendMessage({to:channelID, message : 'This functionality is still being developed.'}); -} -messageHandlers.default = function(bot, channelID, userID, cmd) -{ - bot.sendMessage({to:channelID, message : 'Either the request was not submitted in a correct fashion, or you do not have authorisation to make the request.\n I will neither confirm nor deny whether either of the previous statements are appropriate to your request.'}); -} - - -messageHandlers.intro = function(bot, channelID, userID, cmd) -{ - - bot.sendMessage({to:channelID, message : 'I am an █████████ ████████████████ analysis █████████, working on behalf of ████████ \nFor a list of options and ████████, use `!00718274602 help`'}) -} -messageHandlers.hello = messageHandlers.intro; -messageHandlers.hi = messageHandlers.intro; -messageHandlers.hey = messageHandlers.intro; -messageHandlers.test = function(bot, channelID, userID, cmd) -{ - - bot.sendMessage({to:channelID, embed : 'I am an █████████ ████████████████ analysis █████████, working on behalf of ████████ \nFor a list of options and ████████, use `!00718274602 help`'}) -} -module.exports = messageHandlers; \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..c66f2f8 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +discord +psycopg2 \ No newline at end of file diff --git a/statusMessage.py b/statusMessage.py new file mode 100644 index 0000000..3df1b16 --- /dev/null +++ b/statusMessage.py @@ -0,0 +1,85 @@ +import discord +import datetime + + + +class statusMessage: + + + messageEmbed = None + message = None + messageState = None + messageText = None + messageFooter = None + channel = None + services = {} + + async def _sendMessage(self): + self.message = await self.channel.send(content=self.messageText, embed = self.messageEmbed) + pass + + async def sendOrUpdate(self): + await self._updateEmbed() + if self.message is not None: + #update + pass + else: + self.message = await self._sendMessage() + #send + + + + async def _editMessage(self): + return + + async def _updateEmbed(self): + if self.messageEmbed is None: + self._createEmbed() + self.messageEmbed.set_footer(text=self._getFooter()) + return + + def _createEmbed(self): + messageEmbed = discord.Embed() + + messageEmbed.add_field(name = "Service", value = self._getEmbedServicesList(), inline=True) + messageEmbed.add_field(name = "Status", value = self._getEmbedStatusList(), inline=True) + messageEmbed.colour = discord.Colour.green() + + self.messageEmbed = messageEmbed + + def _getEmbedServicesList(self): + servicesTxt = "" + for service in self.services: + servicesTxt = servicesTxt + "> %s\n" % (service) + return servicesTxt + + def _getEmbedStatusList(self): + statusText = "" + for service in self.services: + statusText = statusText + "%s `%s`\n" % (self.services[service]['symbol'],self.services[service]['text']) + return statusText + + + def _getFooter(self): + return "Last updated %s" % datetime.datetime.now().strftime(r'%Y-%b-%d %H:%M:%S') + + + def checkServices(self): + services = self.services + services["bot"] = {"symbol":"🟩","text":"active"} + services["Vserver - Hordelings"] = {"symbol":"🟩","text":"online [?/10]"} + services["Vserver - deltaPals"] = {"symbol":"🟨","text":"offline warm"} + services["Presence"] = {"symbol":"🟩","text":"active"} + services["LaserScraper"] = {"symbol":"🟩","text":"idle"} + + return + + def __init__(self, channel ,message = None): + if message is not None: + self.message = message + else: + self.messageText = """**Information requested:** Service status +Beginning report...""" + self.channel = channel #discord.channel + + \ No newline at end of file From f307556ccbb9a65c34fcc49b11ed2c42b73dcaec Mon Sep 17 00:00:00 2001 From: C'tri Date: Tue, 9 Mar 2021 13:45:18 +0000 Subject: [PATCH 02/19] Switched from using the "bot" framework to a more generic one * bot.py now is a class that implements discord.client * bot.py now has a recurring task that will form the pillar of updating status messages. * handlers.py now includes a "ping command" * moved statusMessage to a "models" folder --- bot.py | 85 +++++++++++++-------- handlers.py | 8 +- statusMessage.py => models/statusMessage.py | 2 - 3 files changed, 60 insertions(+), 35 deletions(-) rename statusMessage.py => models/statusMessage.py (99%) diff --git a/bot.py b/bot.py index 08b5676..96b9f38 100644 --- a/bot.py +++ b/bot.py @@ -1,51 +1,74 @@ #pip install -U git+https://github.com/Rapptz/discord.py import discord -from discord.ext import commands import random import json import asyncio import re import handlers -description = '''An example bot to showcase the discord.ext.commands extension -module. -There are a number of utility commands being showcased here.''' -bot = commands.Bot(command_prefix='!', description=description) +class Warforged(discord.Client): + + _statusMessages = [] + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.statusUpdaterLoop = self.loop.create_task(self.statusUpdater()) -@bot.event -async def on_message(message): - global bot - selfID = bot.user.id - if message.author.id != selfID: #recursion check. do not react to your own messages + + async def statusUpdater(self): + await self.wait_until_ready() + channel = self.get_channel(689778243225780338) # channel ID goes here + counter = 0 + while not self.is_closed(): + counter += 1 + await channel.send(counter) + await asyncio.sleep(20) # task runs every 60 seconds + + + def registerStatusMessage(self,message): + + pass + #when a message is registered, check for other statusMessages in the channel. + #delete other messages in the channel (they will be older) + #store it in list. + #regularly (every 10 seconds) check list of statusMessages and run their update function. + + + + async def on_message(self,message): + + selfID = self.user.id + if message.author.id != selfID: #recursion check. do not react to your own messages + - - regexText = r'(!0071?8?2?7?4?6?0?2? |\/0071?8?2?7?4?6?0?2? ){1}([a-zA-Z0-9 ]*){1}(-[a-zA-z]*)?' - if re.search(regexText,message.content): - pieces = re.split(regexText,message.content) - await handlers.cmdGeneral(message,pieces,bot) - elif message.guild is None: - regexText = r'([a-zA-Z0-9 ]*){1}(-[a-zA-z]*)?' + regexText = r'(!0071?8?2?7?4?6?0?2? |\/0071?8?2?7?4?6?0?2? ){1}([a-zA-Z0-9 ]*){1}(-[a-zA-z]*)?' if re.search(regexText,message.content): pieces = re.split(regexText,message.content) - pieces = ['','',pieces[1],pieces[2], ''] - await handlers.cmdGeneral(message,pieces,bot) + await handlers.cmdGeneral(message,pieces,self) + elif message.guild is None: + regexText = r'([a-zA-Z0-9 ]*){1}(-[a-zA-z]*)?' + if re.search(regexText,message.content): + pieces = re.split(regexText,message.content) + pieces = ['','',pieces[1],pieces[2], ''] + await handlers.cmdGeneral(message,pieces,self) - elif re.search(r'(00718274602)',message.content): - await handlers.reactTrophies(message) - elif re.search(r'(007[0-9]*)',message.content): - await handlers.reactEyes(message) - - -@bot.event -async def on_ready(): - print('Logged in as') - print(bot.user.name) - print(bot.user.id) - print('------') + elif re.search(r'(00718274602)',message.content): + await handlers.reactTrophies(message) + elif re.search(r'(007[0-9]*)',message.content): + await handlers.reactEyes(message) + + + async def on_ready(self): + print('Logged in as') + print(self.user.name) + print(self.user.id) + print('------') if __name__ == "__main__": + bot = Warforged() with open("auth.json",'r') as json_file: config = json.load(json_file) bot.run(config['token']) + + diff --git a/handlers.py b/handlers.py index 64679e9..4bd1efd 100644 --- a/handlers.py +++ b/handlers.py @@ -3,7 +3,7 @@ import platform import math import random -from statusMessage import * +from models.statusMessage import * from SQLconnector import * async def reactEyes(message): @@ -42,6 +42,8 @@ async def cmdGeneral(message, pieces, bot): await cmdHistorical(message) elif command.lower() == "morning" or command.lower() == "good morning": await cmdMorning(message) + elif command.lower() == "ping": + await cmdPing(message,bot) else: await cmdNotFound(message) @@ -96,7 +98,9 @@ async def cmdStatus(message): embed.add_field(name="Blocking | Blocked by",value = "0 | 0") await message.channel.send(embed=embed) - +async def cmdPing(message,bot): + content = """> Channel - %s,%s\n> you - %s,%s\n> your message - %s """ % (message.channel.id, message.channel.type.name, message.author.id, message.author.display_name, message.id ) + await message.channel.send(content) async def cmdRedact(message, bot): diff --git a/statusMessage.py b/models/statusMessage.py similarity index 99% rename from statusMessage.py rename to models/statusMessage.py index 3df1b16..f2492a5 100644 --- a/statusMessage.py +++ b/models/statusMessage.py @@ -4,8 +4,6 @@ class statusMessage: - - messageEmbed = None message = None messageState = None From 0ca8da9f28355ca2d21f25ecf1c3fd0461aef30e Mon Sep 17 00:00:00 2001 From: C'tri Date: Tue, 9 Mar 2021 20:02:24 +0000 Subject: [PATCH 03/19] initial efforts toward service monitoring --- bot.py | 40 +++++++++++++++++--- handlers.py | 51 ++------------------------ models/service.py | 16 ++++++++ models/serviceMonitor.py | 48 ++++++++++++++++++++++++ models/statusMessage.py | 61 +++++++++++++------------------ serviceMonitors/serviceValheim.py | 14 +++++++ serviceMonitors/valheim.py | 7 ++++ services.json | 26 +++++++++++++ 8 files changed, 174 insertions(+), 89 deletions(-) create mode 100644 models/service.py create mode 100644 models/serviceMonitor.py create mode 100644 serviceMonitors/serviceValheim.py create mode 100644 serviceMonitors/valheim.py create mode 100644 services.json diff --git a/bot.py b/bot.py index 96b9f38..247d477 100644 --- a/bot.py +++ b/bot.py @@ -5,10 +5,11 @@ import asyncio import re import handlers - +import models.serviceMonitor class Warforged(discord.Client): _statusMessages = [] + serviceMonitorInstance = None def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -19,20 +20,43 @@ async def statusUpdater(self): await self.wait_until_ready() channel = self.get_channel(689778243225780338) # channel ID goes here counter = 0 + + + + while not self.is_closed(): counter += 1 - await channel.send(counter) - await asyncio.sleep(20) # task runs every 60 seconds + + await asyncio.sleep(60) # task runs every 60 seconds + await self.updateStatusMessages() - def registerStatusMessage(self,message): + async def updateStatusMessages(self): + #service check should go here + #status messages should be updated with text + for statusM in self._statusMessages: + await statusM.sendOrUpdate() - pass + async def registerStatusMessage(self,report): + message = report.message #when a message is registered, check for other statusMessages in the channel. #delete other messages in the channel (they will be older) #store it in list. #regularly (every 10 seconds) check list of statusMessages and run their update function. - + async for history in message.channel.history(limit=100): + if history.author == self.user and history != message: + if re.match(r'\*\*Information requested:\*\* Service status',history.content): + print("FOUND a definite message\t%s" % (history.content)) + await history.delete() + + # self._statusMessages.remove(history) + + + + + + self._statusMessages.append(report) + async def on_message(self,message): @@ -60,10 +84,14 @@ async def on_message(self,message): async def on_ready(self): + + print('Logged in as') print(self.user.name) print(self.user.id) print('------') + self.serviceMonitorInstance = await models.serviceMonitor.getActiveMonitor() + await self.serviceMonitorInstance.doStatusUpdate() if __name__ == "__main__": bot = Warforged() diff --git a/handlers.py b/handlers.py index 4bd1efd..766ce3e 100644 --- a/handlers.py +++ b/handlers.py @@ -33,7 +33,7 @@ async def cmdGeneral(message, pieces, bot): await message.channel.send('`An error has been encountered. Request not processed`') if command.lower() == "status": - await cmdStatus(message) + await cmdStatus(message,bot) elif command.lower() == "redact": await cmdRedact(message,bot) elif command.lower() == "help": @@ -48,55 +48,12 @@ async def cmdGeneral(message, pieces, bot): await cmdNotFound(message) -async def cmdStatus(message): +async def cmdStatus(message,bot): report = statusMessage(channel=message.channel) - report.checkServices() await report.sendOrUpdate() - return - mesesge ="""**Information Requested: monitored system status.** - Beginning report. + await bot.registerStatusMessage(report) + bot.doStatusUpdate() - """ - embed = discord.Embed() - embed.title = "System status" - embed.add_field(name = "Service", value = "> 00719274602\nDatabase", inline=True) - embed.add_field(name = "Status", value = '`✅ Active`\t%s\n' % platform.node(), inline=True) - - try: - conn = connectToSource() - - cursor = conn.cursor() - sql = """ with data as ( - select row_number() over - ( - partition by healthstatus order by finished desc ) as row - , * from public."jobsView" - ) - select * from data - where finished is null or - (finished is not null and row <= 3 and row > 0) - order by finished desc, started asc""" - cursor.execute(sql) - results = cursor.fetchall() - embed.add_field(name = "DB Status", value = "`Connected` ✅", inline=True) - except: - embed.add_field(name = "DB Status", value = "`OFfline` ❌", inline=True) - - await message.channel.send(embed=embed) - embed = discord.Embed() - embed.title = "Active and recent ETL jobs" - - - for result in results: - statusString = "%s **%s**" %(result[4][-5:],result[1]) - if result[11] is not None: - statusString = "%s, %s%%" % (statusString, result[11]) - embed.add_field(name = statusString, value = "%s" % (result[3]+" "*40)[:40]) - completionTime = "%s" %(result[7]) - completionTime = completionTime[0:19] - embed.add_field(name="Completion time",value = "%s" % (completionTime),inline=True) - embed.add_field(name="Blocking | Blocked by",value = "0 | 0") - await message.channel.send(embed=embed) async def cmdPing(message,bot): content = """> Channel - %s,%s\n> you - %s,%s\n> your message - %s """ % (message.channel.id, message.channel.type.name, message.author.id, message.author.display_name, message.id ) diff --git a/models/service.py b/models/service.py new file mode 100644 index 0000000..57b6692 --- /dev/null +++ b/models/service.py @@ -0,0 +1,16 @@ +import json + + + +class Service(): + serviceName = "" + serviceType = "" + def __init__(self,serviceName,serviceType): + self.serviceName = serviceName + self.serviceType = serviceType + + def checkService(self): + returnObject = {"statusEmoji":"⬛","statusText":"Generic service found!"} + + + diff --git a/models/serviceMonitor.py b/models/serviceMonitor.py new file mode 100644 index 0000000..683a11c --- /dev/null +++ b/models/serviceMonitor.py @@ -0,0 +1,48 @@ + +import json +from models.service import * +from serviceMonitors.serviceValheim import * + +_monitor = None + + +async def getActiveMonitor(): + global _monitor + if _monitor is None: + _monitor = monitor() + await _monitor.doServiceUpdate() + return _monitor + + +class monitor: + _services = [] + def __init__(self): + self.loadConfig() + + + def getServiceListText(self): + return "" + + def getServiceStatusText(self): + return "" + + + def loadConfig(self): + self._services = [] + configFile = open("services.json","r") + servicesList = json.load(configFile) + for serviceJSON in servicesList: + if serviceJSON["serviceType"] == "valheim": + valheimServer = ServiceValheim(serviceJSON["serviceName"],serviceJSON["serviceType"],"IP ADDRESS", "STATUS PORT") + self._services.append(valheimServer) + else: + genericService = Service(serviceJSON["serviceName"],serviceJSON["serviceType"]) + self._services.append(genericService) + + async def doServiceUpdate(self): + print("Ready to update all services") + +if __name__ == "__main__": + monitor = getActiveMonitor() + print(monitor) + \ No newline at end of file diff --git a/models/statusMessage.py b/models/statusMessage.py index f2492a5..e8c7197 100644 --- a/models/statusMessage.py +++ b/models/statusMessage.py @@ -1,6 +1,6 @@ import discord import datetime - +import models.serviceMonitor class statusMessage: @@ -9,68 +9,57 @@ class statusMessage: messageState = None messageText = None messageFooter = None + servicesListText = "None" + servicesStatusText = "⬛ no services checked" channel = None - services = {} + + def __init__(self): + self.servicesListText = models.serviceMonitor.getActiveMonitor().getServiceListText() + self.servicesStatusText = models.serviceMonitor.getActiveMonitor().getServiceStatusText() async def _sendMessage(self): - self.message = await self.channel.send(content=self.messageText, embed = self.messageEmbed) - pass + newMessage = await self.channel.send(content=self.messageText, embed = self.messageEmbed) + self.message = newMessage + async def sendOrUpdate(self): - await self._updateEmbed() + self._refreshEmbed() if self.message is not None: - #update - pass + await self.message.edit(embed=self.messageEmbed) + return else: - self.message = await self._sendMessage() + await self._sendMessage() + return #send - + async def _editMessage(self): return - async def _updateEmbed(self): - if self.messageEmbed is None: - self._createEmbed() - self.messageEmbed.set_footer(text=self._getFooter()) - return - def _createEmbed(self): + def _refreshEmbed(self): messageEmbed = discord.Embed() - messageEmbed.add_field(name = "Service", value = self._getEmbedServicesList(), inline=True) - messageEmbed.add_field(name = "Status", value = self._getEmbedStatusList(), inline=True) + messageEmbed.add_field(name = "Service", value = self.servicesListText, inline=True) + messageEmbed.add_field(name = "Status", value = self.servicesStatusText, inline=True) messageEmbed.colour = discord.Colour.green() + messageEmbed.set_footer(text=self._getFooter()) self.messageEmbed = messageEmbed + return - def _getEmbedServicesList(self): - servicesTxt = "" - for service in self.services: - servicesTxt = servicesTxt + "> %s\n" % (service) - return servicesTxt + def setEmbedServicesList(self,string): + self.servicesListText = string - def _getEmbedStatusList(self): - statusText = "" - for service in self.services: - statusText = statusText + "%s `%s`\n" % (self.services[service]['symbol'],self.services[service]['text']) - return statusText + def setEmbedStatusList(self,string): + self.servicesStatusText = string def _getFooter(self): return "Last updated %s" % datetime.datetime.now().strftime(r'%Y-%b-%d %H:%M:%S') - def checkServices(self): - services = self.services - services["bot"] = {"symbol":"🟩","text":"active"} - services["Vserver - Hordelings"] = {"symbol":"🟩","text":"online [?/10]"} - services["Vserver - deltaPals"] = {"symbol":"🟨","text":"offline warm"} - services["Presence"] = {"symbol":"🟩","text":"active"} - services["LaserScraper"] = {"symbol":"🟩","text":"idle"} - - return def __init__(self, channel ,message = None): if message is not None: diff --git a/serviceMonitors/serviceValheim.py b/serviceMonitors/serviceValheim.py new file mode 100644 index 0000000..5c817c5 --- /dev/null +++ b/serviceMonitors/serviceValheim.py @@ -0,0 +1,14 @@ +import requests +from models.service import * + +#all service requests should get a name, type in JSON, then specific config settings + +class ServiceValheim(Service): + serverHost = '' + serverStatusPort = '' + def __init__(self,serviceName, serviceType,serverHost, serverStatusPort): + super().__init__(serviceName,serviceType) + self.serverHost = serverHost + self.serverStatusPort = serverStatusPort + + \ No newline at end of file diff --git a/serviceMonitors/valheim.py b/serviceMonitors/valheim.py new file mode 100644 index 0000000..06a6e50 --- /dev/null +++ b/serviceMonitors/valheim.py @@ -0,0 +1,7 @@ +import requests + + +#all service requests should get a name, type in JSON, then specific config settings + +def checkService(configName) + \ No newline at end of file diff --git a/services.json b/services.json new file mode 100644 index 0000000..625600a --- /dev/null +++ b/services.json @@ -0,0 +1,26 @@ +[ + { + "serviceName": "hordelings", + "serviceType": "valheim", + "host": "192.168.0.134", + "statusPort": 2459 + }, + { + "serviceName": "deltaPals", + "serviceType": "valheim", + "host": "192.168.0.134", + "statusPort": 3303 + }, + { + "serviceName": "presence", + "serviceType": "application", + "processName": "presence", + "processHost": "192.168.???.??" + }, + { + "serviceName": "LaserScraper", + "serviceType": "application", + "processName": "CmdMenus.py", + "processHost": "192.168.???.??" + } +] \ No newline at end of file From 7fb0258b594ede32b9dd50d0015b85f7ee5a0e22 Mon Sep 17 00:00:00 2001 From: C'tri Date: Tue, 9 Mar 2021 20:02:24 +0000 Subject: [PATCH 04/19] initial efforts toward service monitoring --- bot.py | 40 +++++++++++++++++--- handlers.py | 51 ++------------------------ models/service.py | 16 ++++++++ models/serviceMonitor.py | 48 ++++++++++++++++++++++++ models/statusMessage.py | 61 +++++++++++++------------------ serviceMonitors/serviceValheim.py | 14 +++++++ serviceMonitors/valheim.py | 7 ++++ 7 files changed, 148 insertions(+), 89 deletions(-) create mode 100644 models/service.py create mode 100644 models/serviceMonitor.py create mode 100644 serviceMonitors/serviceValheim.py create mode 100644 serviceMonitors/valheim.py diff --git a/bot.py b/bot.py index 96b9f38..247d477 100644 --- a/bot.py +++ b/bot.py @@ -5,10 +5,11 @@ import asyncio import re import handlers - +import models.serviceMonitor class Warforged(discord.Client): _statusMessages = [] + serviceMonitorInstance = None def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -19,20 +20,43 @@ async def statusUpdater(self): await self.wait_until_ready() channel = self.get_channel(689778243225780338) # channel ID goes here counter = 0 + + + + while not self.is_closed(): counter += 1 - await channel.send(counter) - await asyncio.sleep(20) # task runs every 60 seconds + + await asyncio.sleep(60) # task runs every 60 seconds + await self.updateStatusMessages() - def registerStatusMessage(self,message): + async def updateStatusMessages(self): + #service check should go here + #status messages should be updated with text + for statusM in self._statusMessages: + await statusM.sendOrUpdate() - pass + async def registerStatusMessage(self,report): + message = report.message #when a message is registered, check for other statusMessages in the channel. #delete other messages in the channel (they will be older) #store it in list. #regularly (every 10 seconds) check list of statusMessages and run their update function. - + async for history in message.channel.history(limit=100): + if history.author == self.user and history != message: + if re.match(r'\*\*Information requested:\*\* Service status',history.content): + print("FOUND a definite message\t%s" % (history.content)) + await history.delete() + + # self._statusMessages.remove(history) + + + + + + self._statusMessages.append(report) + async def on_message(self,message): @@ -60,10 +84,14 @@ async def on_message(self,message): async def on_ready(self): + + print('Logged in as') print(self.user.name) print(self.user.id) print('------') + self.serviceMonitorInstance = await models.serviceMonitor.getActiveMonitor() + await self.serviceMonitorInstance.doStatusUpdate() if __name__ == "__main__": bot = Warforged() diff --git a/handlers.py b/handlers.py index 4bd1efd..766ce3e 100644 --- a/handlers.py +++ b/handlers.py @@ -33,7 +33,7 @@ async def cmdGeneral(message, pieces, bot): await message.channel.send('`An error has been encountered. Request not processed`') if command.lower() == "status": - await cmdStatus(message) + await cmdStatus(message,bot) elif command.lower() == "redact": await cmdRedact(message,bot) elif command.lower() == "help": @@ -48,55 +48,12 @@ async def cmdGeneral(message, pieces, bot): await cmdNotFound(message) -async def cmdStatus(message): +async def cmdStatus(message,bot): report = statusMessage(channel=message.channel) - report.checkServices() await report.sendOrUpdate() - return - mesesge ="""**Information Requested: monitored system status.** - Beginning report. + await bot.registerStatusMessage(report) + bot.doStatusUpdate() - """ - embed = discord.Embed() - embed.title = "System status" - embed.add_field(name = "Service", value = "> 00719274602\nDatabase", inline=True) - embed.add_field(name = "Status", value = '`✅ Active`\t%s\n' % platform.node(), inline=True) - - try: - conn = connectToSource() - - cursor = conn.cursor() - sql = """ with data as ( - select row_number() over - ( - partition by healthstatus order by finished desc ) as row - , * from public."jobsView" - ) - select * from data - where finished is null or - (finished is not null and row <= 3 and row > 0) - order by finished desc, started asc""" - cursor.execute(sql) - results = cursor.fetchall() - embed.add_field(name = "DB Status", value = "`Connected` ✅", inline=True) - except: - embed.add_field(name = "DB Status", value = "`OFfline` ❌", inline=True) - - await message.channel.send(embed=embed) - embed = discord.Embed() - embed.title = "Active and recent ETL jobs" - - - for result in results: - statusString = "%s **%s**" %(result[4][-5:],result[1]) - if result[11] is not None: - statusString = "%s, %s%%" % (statusString, result[11]) - embed.add_field(name = statusString, value = "%s" % (result[3]+" "*40)[:40]) - completionTime = "%s" %(result[7]) - completionTime = completionTime[0:19] - embed.add_field(name="Completion time",value = "%s" % (completionTime),inline=True) - embed.add_field(name="Blocking | Blocked by",value = "0 | 0") - await message.channel.send(embed=embed) async def cmdPing(message,bot): content = """> Channel - %s,%s\n> you - %s,%s\n> your message - %s """ % (message.channel.id, message.channel.type.name, message.author.id, message.author.display_name, message.id ) diff --git a/models/service.py b/models/service.py new file mode 100644 index 0000000..57b6692 --- /dev/null +++ b/models/service.py @@ -0,0 +1,16 @@ +import json + + + +class Service(): + serviceName = "" + serviceType = "" + def __init__(self,serviceName,serviceType): + self.serviceName = serviceName + self.serviceType = serviceType + + def checkService(self): + returnObject = {"statusEmoji":"⬛","statusText":"Generic service found!"} + + + diff --git a/models/serviceMonitor.py b/models/serviceMonitor.py new file mode 100644 index 0000000..683a11c --- /dev/null +++ b/models/serviceMonitor.py @@ -0,0 +1,48 @@ + +import json +from models.service import * +from serviceMonitors.serviceValheim import * + +_monitor = None + + +async def getActiveMonitor(): + global _monitor + if _monitor is None: + _monitor = monitor() + await _monitor.doServiceUpdate() + return _monitor + + +class monitor: + _services = [] + def __init__(self): + self.loadConfig() + + + def getServiceListText(self): + return "" + + def getServiceStatusText(self): + return "" + + + def loadConfig(self): + self._services = [] + configFile = open("services.json","r") + servicesList = json.load(configFile) + for serviceJSON in servicesList: + if serviceJSON["serviceType"] == "valheim": + valheimServer = ServiceValheim(serviceJSON["serviceName"],serviceJSON["serviceType"],"IP ADDRESS", "STATUS PORT") + self._services.append(valheimServer) + else: + genericService = Service(serviceJSON["serviceName"],serviceJSON["serviceType"]) + self._services.append(genericService) + + async def doServiceUpdate(self): + print("Ready to update all services") + +if __name__ == "__main__": + monitor = getActiveMonitor() + print(monitor) + \ No newline at end of file diff --git a/models/statusMessage.py b/models/statusMessage.py index f2492a5..e8c7197 100644 --- a/models/statusMessage.py +++ b/models/statusMessage.py @@ -1,6 +1,6 @@ import discord import datetime - +import models.serviceMonitor class statusMessage: @@ -9,68 +9,57 @@ class statusMessage: messageState = None messageText = None messageFooter = None + servicesListText = "None" + servicesStatusText = "⬛ no services checked" channel = None - services = {} + + def __init__(self): + self.servicesListText = models.serviceMonitor.getActiveMonitor().getServiceListText() + self.servicesStatusText = models.serviceMonitor.getActiveMonitor().getServiceStatusText() async def _sendMessage(self): - self.message = await self.channel.send(content=self.messageText, embed = self.messageEmbed) - pass + newMessage = await self.channel.send(content=self.messageText, embed = self.messageEmbed) + self.message = newMessage + async def sendOrUpdate(self): - await self._updateEmbed() + self._refreshEmbed() if self.message is not None: - #update - pass + await self.message.edit(embed=self.messageEmbed) + return else: - self.message = await self._sendMessage() + await self._sendMessage() + return #send - + async def _editMessage(self): return - async def _updateEmbed(self): - if self.messageEmbed is None: - self._createEmbed() - self.messageEmbed.set_footer(text=self._getFooter()) - return - def _createEmbed(self): + def _refreshEmbed(self): messageEmbed = discord.Embed() - messageEmbed.add_field(name = "Service", value = self._getEmbedServicesList(), inline=True) - messageEmbed.add_field(name = "Status", value = self._getEmbedStatusList(), inline=True) + messageEmbed.add_field(name = "Service", value = self.servicesListText, inline=True) + messageEmbed.add_field(name = "Status", value = self.servicesStatusText, inline=True) messageEmbed.colour = discord.Colour.green() + messageEmbed.set_footer(text=self._getFooter()) self.messageEmbed = messageEmbed + return - def _getEmbedServicesList(self): - servicesTxt = "" - for service in self.services: - servicesTxt = servicesTxt + "> %s\n" % (service) - return servicesTxt + def setEmbedServicesList(self,string): + self.servicesListText = string - def _getEmbedStatusList(self): - statusText = "" - for service in self.services: - statusText = statusText + "%s `%s`\n" % (self.services[service]['symbol'],self.services[service]['text']) - return statusText + def setEmbedStatusList(self,string): + self.servicesStatusText = string def _getFooter(self): return "Last updated %s" % datetime.datetime.now().strftime(r'%Y-%b-%d %H:%M:%S') - def checkServices(self): - services = self.services - services["bot"] = {"symbol":"🟩","text":"active"} - services["Vserver - Hordelings"] = {"symbol":"🟩","text":"online [?/10]"} - services["Vserver - deltaPals"] = {"symbol":"🟨","text":"offline warm"} - services["Presence"] = {"symbol":"🟩","text":"active"} - services["LaserScraper"] = {"symbol":"🟩","text":"idle"} - - return def __init__(self, channel ,message = None): if message is not None: diff --git a/serviceMonitors/serviceValheim.py b/serviceMonitors/serviceValheim.py new file mode 100644 index 0000000..5c817c5 --- /dev/null +++ b/serviceMonitors/serviceValheim.py @@ -0,0 +1,14 @@ +import requests +from models.service import * + +#all service requests should get a name, type in JSON, then specific config settings + +class ServiceValheim(Service): + serverHost = '' + serverStatusPort = '' + def __init__(self,serviceName, serviceType,serverHost, serverStatusPort): + super().__init__(serviceName,serviceType) + self.serverHost = serverHost + self.serverStatusPort = serverStatusPort + + \ No newline at end of file diff --git a/serviceMonitors/valheim.py b/serviceMonitors/valheim.py new file mode 100644 index 0000000..06a6e50 --- /dev/null +++ b/serviceMonitors/valheim.py @@ -0,0 +1,7 @@ +import requests + + +#all service requests should get a name, type in JSON, then specific config settings + +def checkService(configName) + \ No newline at end of file From 4bb4dcdb72c487a2d533d8a8c85158321f18823e Mon Sep 17 00:00:00 2001 From: C'tri Date: Wed, 10 Mar 2021 12:05:20 +0000 Subject: [PATCH 05/19] Update .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 1f2fa27..b72cf22 100644 --- a/.gitignore +++ b/.gitignore @@ -108,3 +108,4 @@ auth.json .vscode/launch.json .vscode/settings.json .vscode/launch.json +services.json From 6c6f1136c0e6e8ffee4e90d9e4f1acb99cff21a5 Mon Sep 17 00:00:00 2001 From: C'tri Date: Wed, 10 Mar 2021 12:08:23 +0000 Subject: [PATCH 06/19] valheim server monitor working --- bot.py | 32 +++++++++------------- handlers.py | 2 +- models/service.py | 8 +++++- models/serviceMonitor.py | 32 +++++++++++++--------- models/statusMessage.py | 17 +++++++----- requirements.txt | 3 ++- serviceMonitors/serviceValheim.py | 45 ++++++++++++++++++++++++++++++- 7 files changed, 98 insertions(+), 41 deletions(-) diff --git a/bot.py b/bot.py index 247d477..774ae41 100644 --- a/bot.py +++ b/bot.py @@ -5,7 +5,7 @@ import asyncio import re import handlers -import models.serviceMonitor +import models.serviceMonitor as serviceMonitor class Warforged(discord.Client): _statusMessages = [] @@ -18,24 +18,22 @@ def __init__(self, *args, **kwargs): async def statusUpdater(self): await self.wait_until_ready() - channel = self.get_channel(689778243225780338) # channel ID goes here + channel = self.get_channel(689960699921039360) # channel ID goes here counter = 0 - - - while not self.is_closed(): counter += 1 - - await asyncio.sleep(60) # task runs every 60 seconds + #await channel.send("%s" % counter) + await asyncio.sleep(5) # task runs every 60 seconds + await self.serviceMonitorInstance.doServiceUpdate() await self.updateStatusMessages() async def updateStatusMessages(self): - #service check should go here - #status messages should be updated with text - for statusM in self._statusMessages: - await statusM.sendOrUpdate() + #service check should go here + #status messages should be updated with text + for statusM in self._statusMessages: + await statusM.sendOrUpdate() async def registerStatusMessage(self,report): message = report.message @@ -48,13 +46,7 @@ async def registerStatusMessage(self,report): if re.match(r'\*\*Information requested:\*\* Service status',history.content): print("FOUND a definite message\t%s" % (history.content)) await history.delete() - # self._statusMessages.remove(history) - - - - - self._statusMessages.append(report) @@ -90,8 +82,10 @@ async def on_ready(self): print(self.user.name) print(self.user.id) print('------') - self.serviceMonitorInstance = await models.serviceMonitor.getActiveMonitor() - await self.serviceMonitorInstance.doStatusUpdate() + + self.serviceMonitorInstance = await serviceMonitor.getActiveMonitor() + + if __name__ == "__main__": bot = Warforged() diff --git a/handlers.py b/handlers.py index 766ce3e..b39c122 100644 --- a/handlers.py +++ b/handlers.py @@ -52,7 +52,7 @@ async def cmdStatus(message,bot): report = statusMessage(channel=message.channel) await report.sendOrUpdate() await bot.registerStatusMessage(report) - bot.doStatusUpdate() + #await bot.updateStatusMessages() async def cmdPing(message,bot): diff --git a/models/service.py b/models/service.py index 57b6692..8528236 100644 --- a/models/service.py +++ b/models/service.py @@ -10,7 +10,13 @@ def __init__(self,serviceName,serviceType): self.serviceType = serviceType def checkService(self): - returnObject = {"statusEmoji":"⬛","statusText":"Generic service found!"} + return + def getFriendlyName(self): + return self.serviceName + def getStatusEmoji(self): + return "⬛" + def getStatusText(self): + return "Unsupported service found!" diff --git a/models/serviceMonitor.py b/models/serviceMonitor.py index 683a11c..adf963b 100644 --- a/models/serviceMonitor.py +++ b/models/serviceMonitor.py @@ -3,15 +3,15 @@ from models.service import * from serviceMonitors.serviceValheim import * -_monitor = None +_staticMonitor = None async def getActiveMonitor(): - global _monitor - if _monitor is None: - _monitor = monitor() - await _monitor.doServiceUpdate() - return _monitor + global _staticMonitor + if _staticMonitor is None: + _staticMonitor = monitor() + await _staticMonitor.doServiceUpdate() + return _staticMonitor class monitor: @@ -20,11 +20,17 @@ def __init__(self): self.loadConfig() - def getServiceListText(self): - return "" + async def getServiceListText(self): + returnString = "" + for service in self._services: + returnString = returnString + "> %s\n" % (service.getFriendlyName()) + return returnString - def getServiceStatusText(self): - return "" + async def getServiceStatusText(self): + returnString = "" + for service in self._services: + returnString = returnString + "%s `%s`\n" % (service.getStatusEmoji(), service.getStatusText()) + return returnString def loadConfig(self): @@ -33,14 +39,16 @@ def loadConfig(self): servicesList = json.load(configFile) for serviceJSON in servicesList: if serviceJSON["serviceType"] == "valheim": - valheimServer = ServiceValheim(serviceJSON["serviceName"],serviceJSON["serviceType"],"IP ADDRESS", "STATUS PORT") + valheimServer = ServiceValheim(serviceJSON["serviceName"],serviceJSON["serviceType"],serviceJSON["host"],serviceJSON["statusPort"]) self._services.append(valheimServer) else: genericService = Service(serviceJSON["serviceName"],serviceJSON["serviceType"]) self._services.append(genericService) async def doServiceUpdate(self): - print("Ready to update all services") + for service in self._services: + service.checkService() + print("all services updated") if __name__ == "__main__": monitor = getActiveMonitor() diff --git a/models/statusMessage.py b/models/statusMessage.py index e8c7197..fba6bc6 100644 --- a/models/statusMessage.py +++ b/models/statusMessage.py @@ -13,9 +13,7 @@ class statusMessage: servicesStatusText = "⬛ no services checked" channel = None - def __init__(self): - self.servicesListText = models.serviceMonitor.getActiveMonitor().getServiceListText() - self.servicesStatusText = models.serviceMonitor.getActiveMonitor().getServiceStatusText() + async def _sendMessage(self): newMessage = await self.channel.send(content=self.messageText, embed = self.messageEmbed) @@ -23,7 +21,7 @@ async def _sendMessage(self): async def sendOrUpdate(self): - self._refreshEmbed() + await self._refreshEmbed() if self.message is not None: await self.message.edit(embed=self.messageEmbed) return @@ -38,9 +36,15 @@ async def _editMessage(self): return - def _refreshEmbed(self): + async def _refreshEmbed(self): + + serviceMonitorInstance = await models.serviceMonitor.getActiveMonitor() + self.servicesListText = await serviceMonitorInstance.getServiceListText() + self.servicesStatusText = await serviceMonitorInstance.getServiceStatusText() + + messageEmbed = discord.Embed() - + messageEmbed.add_field(name = "Service", value = self.servicesListText, inline=True) messageEmbed.add_field(name = "Status", value = self.servicesStatusText, inline=True) messageEmbed.colour = discord.Colour.green() @@ -67,6 +71,7 @@ def __init__(self, channel ,message = None): else: self.messageText = """**Information requested:** Service status Beginning report...""" + self.channel = channel #discord.channel \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index c66f2f8..3267af9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ discord -psycopg2 \ No newline at end of file +psycopg2 +requests \ No newline at end of file diff --git a/serviceMonitors/serviceValheim.py b/serviceMonitors/serviceValheim.py index 5c817c5..73b28f4 100644 --- a/serviceMonitors/serviceValheim.py +++ b/serviceMonitors/serviceValheim.py @@ -1,4 +1,5 @@ import requests +import json from models.service import * #all service requests should get a name, type in JSON, then specific config settings @@ -6,9 +7,51 @@ class ServiceValheim(Service): serverHost = '' serverStatusPort = '' + serviceName = '' + serviceType ='' + + _statusEmoji = ":black_square:" + _statusText = "Not checked yet" def __init__(self,serviceName, serviceType,serverHost, serverStatusPort): super().__init__(serviceName,serviceType) self.serverHost = serverHost self.serverStatusPort = serverStatusPort + self.serviceType = serviceType + self.serviceName = serviceName + + + def checkService(self): + url = "http://%s:%s/status.json" % (self.serverHost,self.serverStatusPort) + try: + response = requests.get(url) + except: + self._statusEmoji = ":red_square:" + self._statusText = "Server unreachable" + return + if response.status_code == 200: + servercont = {} + try: + serverStatus = json.loads(response.content) + except: + self._statusEmoji = ":red_square:" + self._statusText = "Invalid response" + if "player_count" in serverStatus: + self._statusEmoji = ":green_square:" + self._statusText = "%s/10 players" % (serverStatus["player_count"]) + else: + self._statusEmoji = ":yellow_square:" + self._statusText = "Probably waking up" + + # throw in the box active but server inactive + return + + def getFriendlyName(self): + return self.serviceName + + def getStatusEmoji(self): + return self._statusEmoji + + def getStatusText(self): + return self._statusText + - \ No newline at end of file From 892a22faf490a16f08017c19cf4fb8aa40a2fc7c Mon Sep 17 00:00:00 2001 From: C'tri Date: Wed, 10 Mar 2021 14:26:53 +0000 Subject: [PATCH 07/19] stability improvements async requests to status.json, and better handling of crashes additionally if someone deletes the statusMessage on discord, it will send a new one instead of crashing. --- bot.py | 1 + models/service.py | 2 +- models/serviceMonitor.py | 3 ++- models/statusMessage.py | 9 +++++++-- requirements.txt | 3 ++- serviceMonitors/serviceValheim.py | 25 ++++++++++++++++++++----- 6 files changed, 33 insertions(+), 10 deletions(-) diff --git a/bot.py b/bot.py index 774ae41..2f2be00 100644 --- a/bot.py +++ b/bot.py @@ -24,6 +24,7 @@ async def statusUpdater(self): while not self.is_closed(): counter += 1 #await channel.send("%s" % counter) + await asyncio.sleep(5) # task runs every 60 seconds await self.serviceMonitorInstance.doServiceUpdate() await self.updateStatusMessages() diff --git a/models/service.py b/models/service.py index 8528236..e2a093e 100644 --- a/models/service.py +++ b/models/service.py @@ -9,7 +9,7 @@ def __init__(self,serviceName,serviceType): self.serviceName = serviceName self.serviceType = serviceType - def checkService(self): + async def checkService(self): return def getFriendlyName(self): diff --git a/models/serviceMonitor.py b/models/serviceMonitor.py index adf963b..e19aadc 100644 --- a/models/serviceMonitor.py +++ b/models/serviceMonitor.py @@ -47,7 +47,8 @@ def loadConfig(self): async def doServiceUpdate(self): for service in self._services: - service.checkService() + print("doServiceUpdate - starting next service %s" % service.getFriendlyName()) + await service.checkService() print("all services updated") if __name__ == "__main__": diff --git a/models/statusMessage.py b/models/statusMessage.py index fba6bc6..3d7091d 100644 --- a/models/statusMessage.py +++ b/models/statusMessage.py @@ -13,7 +13,7 @@ class statusMessage: servicesStatusText = "⬛ no services checked" channel = None - + destroyed = False async def _sendMessage(self): newMessage = await self.channel.send(content=self.messageText, embed = self.messageEmbed) @@ -22,8 +22,13 @@ async def _sendMessage(self): async def sendOrUpdate(self): await self._refreshEmbed() + if self.message is not None: - await self.message.edit(embed=self.messageEmbed) + try: + await self.message.edit(embed=self.messageEmbed) + except Exception as e: + print("Couldn't updpate status message \n %s" % e) + self.message = None return else: await self._sendMessage() diff --git a/requirements.txt b/requirements.txt index 3267af9..0ccafe7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ discord psycopg2 -requests \ No newline at end of file +requests +requests_threads \ No newline at end of file diff --git a/serviceMonitors/serviceValheim.py b/serviceMonitors/serviceValheim.py index 73b28f4..38c15b6 100644 --- a/serviceMonitors/serviceValheim.py +++ b/serviceMonitors/serviceValheim.py @@ -1,4 +1,5 @@ -import requests + +import grequests import json from models.service import * @@ -10,8 +11,10 @@ class ServiceValheim(Service): serviceName = '' serviceType ='' - _statusEmoji = ":black_square:" + _statusEmoji = ":black_large_square:" _statusText = "Not checked yet" + + def __init__(self,serviceName, serviceType,serverHost, serverStatusPort): super().__init__(serviceName,serviceType) self.serverHost = serverHost @@ -20,14 +23,21 @@ def __init__(self,serviceName, serviceType,serverHost, serverStatusPort): self.serviceName = serviceName - def checkService(self): + async def checkService(self): url = "http://%s:%s/status.json" % (self.serverHost,self.serverStatusPort) try: - response = requests.get(url) - except: + requests = [grequests.get(url,timeout=5)] + responses = grequests.map(requests) + response = responses[0] + if response == None: + raise Exception("Did not receive response from server") + + except Exception as e: self._statusEmoji = ":red_square:" self._statusText = "Server unreachable" + print(e) return + if response.status_code == 200: servercont = {} try: @@ -54,4 +64,9 @@ def getStatusEmoji(self): def getStatusText(self): return self._statusText + def tryStartService(): + return + def tryStopService(): + + return From 2d9a0642a7f34079dfec8acc865932171399f56e Mon Sep 17 00:00:00 2001 From: C'tri Date: Wed, 10 Mar 2021 16:16:14 +0000 Subject: [PATCH 08/19] Bot can now start and stop services service monitor can execute startup and shutdown commands specified in the json valheim service now tracks when the server was last occupied and tries to shutdown after 20 minutes --- bot.py | 5 +++- handlers.py | 10 ++++++- models/serviceMonitor.py | 7 +++-- serviceControls/launchValheimHordelings.bat | 2 ++ serviceMonitors/serviceValheim.py | 32 +++++++++++++++------ serviceMonitors/valheim.py | 7 ----- 6 files changed, 42 insertions(+), 21 deletions(-) create mode 100644 serviceControls/launchValheimHordelings.bat delete mode 100644 serviceMonitors/valheim.py diff --git a/bot.py b/bot.py index 2f2be00..2b4b7ee 100644 --- a/bot.py +++ b/bot.py @@ -28,6 +28,7 @@ async def statusUpdater(self): await asyncio.sleep(5) # task runs every 60 seconds await self.serviceMonitorInstance.doServiceUpdate() await self.updateStatusMessages() + print("heartbeat") async def updateStatusMessages(self): @@ -58,10 +59,12 @@ async def on_message(self,message): if message.author.id != selfID: #recursion check. do not react to your own messages - regexText = r'(!0071?8?2?7?4?6?0?2? |\/0071?8?2?7?4?6?0?2? ){1}([a-zA-Z0-9 ]*){1}(-[a-zA-z]*)?' + regexText = r'(!0071?8?2?7?4?6?0?2? |\/0071?8?2?7?4?6?0?2? ){1}([a-zA-Z0-9 ]*){1}(-[a-zA-z]*)?' if re.search(regexText,message.content): pieces = re.split(regexText,message.content) await handlers.cmdGeneral(message,pieces,self) + elif re.search(r'[!?/](0071?8?2?7?4?6?0?2? )?valh[ei]{2}m', message.content): + await handlers.cmdValheim(message,self.serviceMonitorInstance,self) elif message.guild is None: regexText = r'([a-zA-Z0-9 ]*){1}(-[a-zA-z]*)?' if re.search(regexText,message.content): diff --git a/handlers.py b/handlers.py index b39c122..6826168 100644 --- a/handlers.py +++ b/handlers.py @@ -5,7 +5,7 @@ import random from models.statusMessage import * from SQLconnector import * - +import serviceMonitors.serviceValheim async def reactEyes(message): try: await message.add_reaction('\N{EYES}') @@ -19,6 +19,14 @@ async def reactTrophies(message): except discord.HTTPException: pass +async def cmdValheim(message,serviceController,bot): + if serviceController is None: + message.channel.send("Unable to connect to service controller - try again in 2 seconds.") + return + for service in serviceController.getServices(): + if isinstance(service,serviceMonitors.serviceValheim.ServiceValheim): + await service.tryStartService() + await message.channel.send("Attempting to activate %s. See `!007 status` for server status." % service.getFriendlyName()) async def cmdGeneral(message, pieces, bot): diff --git a/models/serviceMonitor.py b/models/serviceMonitor.py index e19aadc..a436594 100644 --- a/models/serviceMonitor.py +++ b/models/serviceMonitor.py @@ -19,6 +19,8 @@ class monitor: def __init__(self): self.loadConfig() + def getServices(self): + return self._services async def getServiceListText(self): returnString = "" @@ -39,7 +41,7 @@ def loadConfig(self): servicesList = json.load(configFile) for serviceJSON in servicesList: if serviceJSON["serviceType"] == "valheim": - valheimServer = ServiceValheim(serviceJSON["serviceName"],serviceJSON["serviceType"],serviceJSON["host"],serviceJSON["statusPort"]) + valheimServer = ServiceValheim(serviceJSON["serviceName"],serviceJSON["serviceType"],serviceJSON["host"],serviceJSON["statusPort"],serviceJSON["startupCommand"],serviceJSON["shutdownCommand"]) self._services.append(valheimServer) else: genericService = Service(serviceJSON["serviceName"],serviceJSON["serviceType"]) @@ -47,9 +49,8 @@ def loadConfig(self): async def doServiceUpdate(self): for service in self._services: - print("doServiceUpdate - starting next service %s" % service.getFriendlyName()) await service.checkService() - print("all services updated") + if __name__ == "__main__": monitor = getActiveMonitor() diff --git a/serviceControls/launchValheimHordelings.bat b/serviceControls/launchValheimHordelings.bat new file mode 100644 index 0000000..7b4f5ab --- /dev/null +++ b/serviceControls/launchValheimHordelings.bat @@ -0,0 +1,2 @@ +docker pull lloesche/valheim-server +docker run -d --name valheim-server-hordelings --cap-add=sys_nice -p 2456-2458:2456-2458/udp -p 2459:80 -v C:\ValheimServers\Hordelings\ValheimConfig:/config -v C:\ValheimServers\Hordelings\ValheimServer:/opt/valheim -e SERVER_NAME="Hordelings" -e WORLD_NAME="Dedicated_hordes" -e SERVER_PASS="Gwent" -e STATUS_HTTP=true lloesche/valheim-server diff --git a/serviceMonitors/serviceValheim.py b/serviceMonitors/serviceValheim.py index 38c15b6..55cba0a 100644 --- a/serviceMonitors/serviceValheim.py +++ b/serviceMonitors/serviceValheim.py @@ -1,6 +1,7 @@ - +import os import grequests import json +import datetime from models.service import * #all service requests should get a name, type in JSON, then specific config settings @@ -10,21 +11,26 @@ class ServiceValheim(Service): serverStatusPort = '' serviceName = '' serviceType ='' - + startupCommand = '' + shutdownCommand = '' + lastOccupied = None _statusEmoji = ":black_large_square:" _statusText = "Not checked yet" - def __init__(self,serviceName, serviceType,serverHost, serverStatusPort): + def __init__(self,serviceName, serviceType,serverHost, serverStatusPort, startupCommand, shutdownCommand): super().__init__(serviceName,serviceType) self.serverHost = serverHost self.serverStatusPort = serverStatusPort self.serviceType = serviceType self.serviceName = serviceName - + self.startupCommand = startupCommand + self.shutdownCommand = shutdownCommand + self.lastOccupied = datetime.datetime.now() async def checkService(self): url = "http://%s:%s/status.json" % (self.serverHost,self.serverStatusPort) + playerCount = 0 try: requests = [grequests.get(url,timeout=5)] responses = grequests.map(requests) @@ -48,10 +54,17 @@ async def checkService(self): if "player_count" in serverStatus: self._statusEmoji = ":green_square:" self._statusText = "%s/10 players" % (serverStatus["player_count"]) + playerCount = serverStatus["player_count"] else: self._statusEmoji = ":yellow_square:" self._statusText = "Probably waking up" - + if playerCount > 0: + self.lastOccupied = datetime.datetime.now() + else: + timeSinceOccupied = datetime.datetime.now() - self.lastOccupied + print("Server [%s]- time since last occupied: %s" % (self.getFriendlyName(),timeSinceOccupied)) + if timeSinceOccupied.seconds > 120: + self.tryStopService() # throw in the box active but server inactive return @@ -64,9 +77,10 @@ def getStatusEmoji(self): def getStatusText(self): return self._statusText - def tryStartService(): - + async def tryStartService(self ): + os.system(self.startupCommand) return - def tryStopService(): - + def tryStopService(self): + os.system(self.shutdownCommand) + return diff --git a/serviceMonitors/valheim.py b/serviceMonitors/valheim.py deleted file mode 100644 index 06a6e50..0000000 --- a/serviceMonitors/valheim.py +++ /dev/null @@ -1,7 +0,0 @@ -import requests - - -#all service requests should get a name, type in JSON, then specific config settings - -def checkService(configName) - \ No newline at end of file From 5fb90a9fbe45b8ae8b8a70ddce6c08653799539c Mon Sep 17 00:00:00 2001 From: C'tri Date: Wed, 10 Mar 2021 17:27:21 +0000 Subject: [PATCH 09/19] added readme --- .gitignore | 1 + readme.md | 28 ++++++++++++++++++++++++++++ serviceMonitors/serviceValheim.py | 4 +++- 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 readme.md diff --git a/.gitignore b/.gitignore index b72cf22..4baafc7 100644 --- a/.gitignore +++ b/.gitignore @@ -109,3 +109,4 @@ auth.json .vscode/settings.json .vscode/launch.json services.json +serviceControls/launchValheimDeltaPals.bat diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..978e2d2 --- /dev/null +++ b/readme.md @@ -0,0 +1,28 @@ +# installation + +Needs an auth.json file in the following format: +```json +{ + "token":"bot token" +} +``` + +and a services.json dictating what it should be looking for, and where. + +```json +[ + { + "serviceName": "hordelings", + "serviceType": "valheim", + "host": "127.0.0.1", + "statusPort": 2459, + "startupCommand": "serviceControls\\startServer.bat", + "shutdownCommand": "serviceControls\\stopServer.bat" + }, + { + "serviceName": "LaserScraper", + "serviceType": "localApplication", + "processName": "CmdMenus.py", + } +] +``` \ No newline at end of file diff --git a/serviceMonitors/serviceValheim.py b/serviceMonitors/serviceValheim.py index 55cba0a..55ee276 100644 --- a/serviceMonitors/serviceValheim.py +++ b/serviceMonitors/serviceValheim.py @@ -63,7 +63,7 @@ async def checkService(self): else: timeSinceOccupied = datetime.datetime.now() - self.lastOccupied print("Server [%s]- time since last occupied: %s" % (self.getFriendlyName(),timeSinceOccupied)) - if timeSinceOccupied.seconds > 120: + if timeSinceOccupied.seconds > 1200: self.tryStopService() # throw in the box active but server inactive return @@ -78,7 +78,9 @@ def getStatusText(self): return self._statusText async def tryStartService(self ): + os.system(self.startupCommand) + self.lastOccupied = datetime.datetime.now() return def tryStopService(self): os.system(self.shutdownCommand) From 10cdd4e808887dff08de517751b987548a4aa6d6 Mon Sep 17 00:00:00 2001 From: C'tri Date: Wed, 10 Mar 2021 17:28:02 +0000 Subject: [PATCH 10/19] Update .gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 4baafc7..d38dfed 100644 --- a/.gitignore +++ b/.gitignore @@ -109,4 +109,4 @@ auth.json .vscode/settings.json .vscode/launch.json services.json -serviceControls/launchValheimDeltaPals.bat +serviceControls/* From 46cfdfb69fd0dab304929d8ec525a555b9fa6b15 Mon Sep 17 00:00:00 2001 From: C'tri Date: Wed, 10 Mar 2021 23:02:10 +0000 Subject: [PATCH 11/19] Delete services.json --- services.json | 26 -------------------------- 1 file changed, 26 deletions(-) delete mode 100644 services.json diff --git a/services.json b/services.json deleted file mode 100644 index 625600a..0000000 --- a/services.json +++ /dev/null @@ -1,26 +0,0 @@ -[ - { - "serviceName": "hordelings", - "serviceType": "valheim", - "host": "192.168.0.134", - "statusPort": 2459 - }, - { - "serviceName": "deltaPals", - "serviceType": "valheim", - "host": "192.168.0.134", - "statusPort": 3303 - }, - { - "serviceName": "presence", - "serviceType": "application", - "processName": "presence", - "processHost": "192.168.???.??" - }, - { - "serviceName": "LaserScraper", - "serviceType": "application", - "processName": "CmdMenus.py", - "processHost": "192.168.???.??" - } -] \ No newline at end of file From 8cc98708ce957d92bdfce89e2ffb43b7c376ec3a Mon Sep 17 00:00:00 2001 From: C'tri Date: Fri, 12 Mar 2021 20:22:00 +0000 Subject: [PATCH 12/19] threading improvements --- bot.py | 39 ++++++++++++++++++++----------- models/statusMessage.py | 2 +- serviceMonitors/serviceValheim.py | 7 +++--- 3 files changed, 30 insertions(+), 18 deletions(-) diff --git a/bot.py b/bot.py index 2b4b7ee..f1fbf20 100644 --- a/bot.py +++ b/bot.py @@ -6,36 +6,47 @@ import re import handlers import models.serviceMonitor as serviceMonitor +import threading + + class Warforged(discord.Client): _statusMessages = [] serviceMonitorInstance = None + statusUpdaterLoop = None def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.statusUpdaterLoop = self.loop.create_task(self.statusUpdater()) + #detached loop not connected to the discord client for checking service - async def statusUpdater(self): - await self.wait_until_ready() - channel = self.get_channel(689960699921039360) # channel ID goes here - counter = 0 + def serviceUpdaterLoop(self): + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + loop.run_until_complete(self.serviceUpdater()) + loop.close() + + async def serviceUpdater(self): while not self.is_closed(): - counter += 1 #await channel.send("%s" % counter) await asyncio.sleep(5) # task runs every 60 seconds await self.serviceMonitorInstance.doServiceUpdate() - await self.updateStatusMessages() - print("heartbeat") + print("thread2-heartbeat") - async def updateStatusMessages(self): + async def updateStatusMessagesLoop(self): #service check should go here #status messages should be updated with text - for statusM in self._statusMessages: - await statusM.sendOrUpdate() + while True: + for statusM in self._statusMessages: + if statusM.destroyed: + self._statusMessages.remove(statusM) + else: + await statusM.sendOrUpdate() + await asyncio.sleep(10) + print("asyncMessage-heartbeat") async def registerStatusMessage(self,report): message = report.message @@ -86,9 +97,11 @@ async def on_ready(self): print(self.user.name) print(self.user.id) print('------') - + self.serviceMonitorInstance = await serviceMonitor.getActiveMonitor() - + self.statusUpdaterLoop = threading.Thread(target=self.serviceUpdaterLoop) + self.statusUpdaterLoop.start() + self.loop.create_task(self.updateStatusMessagesLoop()) if __name__ == "__main__": diff --git a/models/statusMessage.py b/models/statusMessage.py index 3d7091d..32beccb 100644 --- a/models/statusMessage.py +++ b/models/statusMessage.py @@ -28,7 +28,7 @@ async def sendOrUpdate(self): await self.message.edit(embed=self.messageEmbed) except Exception as e: print("Couldn't updpate status message \n %s" % e) - self.message = None + destroyed = True return else: await self._sendMessage() diff --git a/serviceMonitors/serviceValheim.py b/serviceMonitors/serviceValheim.py index 55ee276..f1a0bdc 100644 --- a/serviceMonitors/serviceValheim.py +++ b/serviceMonitors/serviceValheim.py @@ -1,5 +1,5 @@ import os -import grequests +import requests import json import datetime from models.service import * @@ -32,9 +32,8 @@ async def checkService(self): url = "http://%s:%s/status.json" % (self.serverHost,self.serverStatusPort) playerCount = 0 try: - requests = [grequests.get(url,timeout=5)] - responses = grequests.map(requests) - response = responses[0] + response = requests.get(url,timeout=5) + if response == None: raise Exception("Did not receive response from server") From c69be36183b3dc3aaf983afec444349b4c5c007b Mon Sep 17 00:00:00 2001 From: C'tri Date: Fri, 12 Mar 2021 20:28:06 +0000 Subject: [PATCH 13/19] computer service --- bot.py | 1 + handlers.py | 3 ++ models/serviceMonitor.py | 24 +++++++++++-- models/statusMessage.py | 9 ++++- requirements.txt | 3 +- serviceMonitors/serviceComputer.py | 56 ++++++++++++++++++++++++++++++ 6 files changed, 91 insertions(+), 5 deletions(-) create mode 100644 serviceMonitors/serviceComputer.py diff --git a/bot.py b/bot.py index 2b4b7ee..7f5ac61 100644 --- a/bot.py +++ b/bot.py @@ -34,6 +34,7 @@ async def statusUpdater(self): async def updateStatusMessages(self): #service check should go here #status messages should be updated with text + for statusM in self._statusMessages: await statusM.sendOrUpdate() diff --git a/handlers.py b/handlers.py index 6826168..7092248 100644 --- a/handlers.py +++ b/handlers.py @@ -27,6 +27,9 @@ async def cmdValheim(message,serviceController,bot): if isinstance(service,serviceMonitors.serviceValheim.ServiceValheim): await service.tryStartService() await message.channel.send("Attempting to activate %s. See `!007 status` for server status." % service.getFriendlyName()) + elif isinstance (service,serviceMonitors.serviceComputer.ServiceComputer): + await service.tryStartService() + await message.channel.send("Sending awake instruction to %s. NOTE: run this again once it's woken up!") async def cmdGeneral(message, pieces, bot): diff --git a/models/serviceMonitor.py b/models/serviceMonitor.py index a436594..6c0a34e 100644 --- a/models/serviceMonitor.py +++ b/models/serviceMonitor.py @@ -2,7 +2,7 @@ import json from models.service import * from serviceMonitors.serviceValheim import * - +from serviceMonitors.serviceComputer import * _staticMonitor = None @@ -25,15 +25,30 @@ def getServices(self): async def getServiceListText(self): returnString = "" for service in self._services: - returnString = returnString + "> %s\n" % (service.getFriendlyName()) + if service.serviceType != "server": + returnString = returnString + "> %s\n" % (service.getFriendlyName()) return returnString async def getServiceStatusText(self): returnString = "" for service in self._services: - returnString = returnString + "%s `%s`\n" % (service.getStatusEmoji(), service.getStatusText()) + if service.serviceType != "server": + returnString = returnString + "%s `%s`\n" % (service.getStatusEmoji(), service.getStatusText()) + return returnString + + async def getServerListText(self): + returnString = "" + for service in self._services: + if service.serviceType == "server": + returnString = returnString + "> %s\n" % (service.getFriendlyName()) return returnString + async def getServerStatusText(self): + returnString = "" + for service in self._services: + if service.serviceType == "server": + returnString = returnString + "%s `%s`\n" % (service.getStatusEmoji(), service.getStatusText()) + return returnString def loadConfig(self): self._services = [] @@ -43,6 +58,9 @@ def loadConfig(self): if serviceJSON["serviceType"] == "valheim": valheimServer = ServiceValheim(serviceJSON["serviceName"],serviceJSON["serviceType"],serviceJSON["host"],serviceJSON["statusPort"],serviceJSON["startupCommand"],serviceJSON["shutdownCommand"]) self._services.append(valheimServer) + elif serviceJSON["serviceType"] == "server": + computerService = ServiceComputer(serviceJSON["serviceName"],serviceJSON["serviceType"],serviceJSON["host"],serviceJSON["shutdownCommand"],serviceJSON["serverMacAddress"]) + self._services.append(computerService) else: genericService = Service(serviceJSON["serviceName"],serviceJSON["serviceType"]) self._services.append(genericService) diff --git a/models/statusMessage.py b/models/statusMessage.py index 3d7091d..e2ecd24 100644 --- a/models/statusMessage.py +++ b/models/statusMessage.py @@ -9,6 +9,8 @@ class statusMessage: messageState = None messageText = None messageFooter = None + serverListText = "None" + serverStatusText = "⬛ no services checked" servicesListText = "None" servicesStatusText = "⬛ no services checked" channel = None @@ -46,12 +48,17 @@ async def _refreshEmbed(self): serviceMonitorInstance = await models.serviceMonitor.getActiveMonitor() self.servicesListText = await serviceMonitorInstance.getServiceListText() self.servicesStatusText = await serviceMonitorInstance.getServiceStatusText() - + self.serverListText = await serviceMonitorInstance.getServerListText() + self.serverStatusText = await serviceMonitorInstance.getServerStatusText() messageEmbed = discord.Embed() + messageEmbed.add_field(name="Server", value = self.serverListText, inline=True) + messageEmbed.add_field(name = "Status", value = self.serverStatusText, inline=True) + messageEmbed.add_field(name = "\u200b", value="\u200b", inline=True) messageEmbed.add_field(name = "Service", value = self.servicesListText, inline=True) messageEmbed.add_field(name = "Status", value = self.servicesStatusText, inline=True) + messageEmbed.colour = discord.Colour.green() messageEmbed.set_footer(text=self._getFooter()) diff --git a/requirements.txt b/requirements.txt index 0ccafe7..7fd44a3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ discord psycopg2 requests -requests_threads \ No newline at end of file +wakeonlan +threading \ No newline at end of file diff --git a/serviceMonitors/serviceComputer.py b/serviceMonitors/serviceComputer.py new file mode 100644 index 0000000..7d44819 --- /dev/null +++ b/serviceMonitors/serviceComputer.py @@ -0,0 +1,56 @@ +import os +import subprocess +from models.service import * +from wakeonlan import send_magic_packet + +#all service requests should get a name, type in JSON, then specific config settings + +class ServiceComputer(Service): + serverMacAddress = '' + serviceName = '' + serviceType ='' + host = '' + shutdownCommand = '' + _statusEmoji = ":black_large_square:" + _statusText = "Not checked yet" + + + def __init__(self,serviceName, serviceType, host, shutdownCommand, serverMacAddress ): + super().__init__(serviceName,serviceType) + + self.serviceType = serviceType + self.serviceName = serviceName + self.serverMacAddress = serverMacAddress + self.shutdownCommand = shutdownCommand + self.host = host + + async def checkService(self): + arg = "c" if os.name == 'posix' else "n" + response=os.system("ping -%s 1 %s" % (arg,self.host)) + + if response == 0: + self._statusEmoji = ":green_square:" + self._statusText = "online" + else: + self._statusEmoji = ":snowflake:" + self._statusText = "offline" + return + + def getFriendlyName(self): + return self.serviceName + + def getStatusEmoji(self): + return self._statusEmoji + + def getStatusText(self): + return self._statusText + + async def tryStartService(self ): + send_magic_packet('18-c0-d4-49-59-d0') + + def tryStopService(self): + os.system(self.shutdownCommand) + +if __name__ == '__main__': + s = ServiceComputer(":desktop: Ramoth","server","serviceControls\\sleepRamoth.bat","18-c0-d4-49-59-d0") + s.tryStartService() \ No newline at end of file From aecfc863523c86ad737ece9b8ccd55161bc69ed9 Mon Sep 17 00:00:00 2001 From: C'tri Date: Sat, 13 Mar 2021 20:32:30 +0000 Subject: [PATCH 14/19] Update requirements.txt --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 7fd44a3..6beecd9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,5 @@ discord psycopg2 requests wakeonlan -threading \ No newline at end of file +threading +wakeonlan \ No newline at end of file From 04eb7153f77d137403ac17d54252d8085582d7fb Mon Sep 17 00:00:00 2001 From: C'tri Date: Sat, 13 Mar 2021 23:02:27 +0000 Subject: [PATCH 15/19] threading and commands ready for production --- bot.py | 28 ++++++++-- handlers.py | 88 +++++------------------------- models/serviceMonitor.py | 11 +++- serviceMonitors/serviceComputer.py | 21 ++++--- 4 files changed, 60 insertions(+), 88 deletions(-) diff --git a/bot.py b/bot.py index f1fbf20..378f126 100644 --- a/bot.py +++ b/bot.py @@ -6,6 +6,7 @@ import re import handlers import models.serviceMonitor as serviceMonitor +import models.statusMessage import threading @@ -62,6 +63,21 @@ async def registerStatusMessage(self,report): # self._statusMessages.remove(history) self._statusMessages.append(report) + async def reactivateHistoricStatusMessages(self): + for guild in self.guilds: + for channel in guild.channels: + if channel.type.name == "text": + try: + async for history in channel.history(limit=100): + if history.author == self.user: + if re.match(r'\*\*Information requested:\*\* Service status',history.content): + print("FOUND a definite message\t%s" % (history.content)) + deadStatusMessage = models.statusMessage.statusMessage(channel,message=history) + self._statusMessages.append(deadStatusMessage) + except Exception as e: + print("ERROR in reactivating historical status messages -\t%s\n%s"% (channel.name,e)) + + async def on_message(self,message): @@ -71,11 +87,13 @@ async def on_message(self,message): regexText = r'(!0071?8?2?7?4?6?0?2? |\/0071?8?2?7?4?6?0?2? ){1}([a-zA-Z0-9 ]*){1}(-[a-zA-z]*)?' - if re.search(regexText,message.content): + if re.search(r'[!?/](0071?8?2?7?4?6?0?2? )?valh[ei]{2}m', message.content): + await handlers.cmdValheim(message,self.serviceMonitorInstance,self) + elif re.search(r'[!?/](0071?8?2?7?4?6?0?2? )?a?waken?', message.content): + await handlers.cmdAwaken(message,self) + elif re.search(regexText,message.content): pieces = re.split(regexText,message.content) await handlers.cmdGeneral(message,pieces,self) - elif re.search(r'[!?/](0071?8?2?7?4?6?0?2? )?valh[ei]{2}m', message.content): - await handlers.cmdValheim(message,self.serviceMonitorInstance,self) elif message.guild is None: regexText = r'([a-zA-Z0-9 ]*){1}(-[a-zA-z]*)?' if re.search(regexText,message.content): @@ -98,10 +116,12 @@ async def on_ready(self): print(self.user.id) print('------') + self.serviceMonitorInstance = await serviceMonitor.getActiveMonitor() self.statusUpdaterLoop = threading.Thread(target=self.serviceUpdaterLoop) self.statusUpdaterLoop.start() - self.loop.create_task(self.updateStatusMessagesLoop()) + self.loop.create_task(self.updateStatusMessagesLoop()) + await self.reactivateHistoricStatusMessages() if __name__ == "__main__": diff --git a/handlers.py b/handlers.py index 7092248..de80687 100644 --- a/handlers.py +++ b/handlers.py @@ -3,6 +3,7 @@ import platform import math import random +import re from models.statusMessage import * from SQLconnector import * import serviceMonitors.serviceValheim @@ -19,7 +20,8 @@ async def reactTrophies(message): except discord.HTTPException: pass -async def cmdValheim(message,serviceController,bot): +async def cmdValheim(message,bot): + serviceController = bot.serviceMonitorInstance if serviceController is None: message.channel.send("Unable to connect to service controller - try again in 2 seconds.") return @@ -30,7 +32,10 @@ async def cmdValheim(message,serviceController,bot): elif isinstance (service,serviceMonitors.serviceComputer.ServiceComputer): await service.tryStartService() await message.channel.send("Sending awake instruction to %s. NOTE: run this again once it's woken up!") - +async def cmdAwaken(message, bot): + computerServices = bot.serviceMonitorInstance.getServices(serviceType = 'server') + for service in computerServices: + await service.tryStartService() async def cmdGeneral(message, pieces, bot): try: @@ -45,16 +50,12 @@ async def cmdGeneral(message, pieces, bot): if command.lower() == "status": await cmdStatus(message,bot) - elif command.lower() == "redact": - await cmdRedact(message,bot) elif command.lower() == "help": await cmdHelp(message) - elif command.lower() == "historical": - await cmdHistorical(message) - elif command.lower() == "morning" or command.lower() == "good morning": - await cmdMorning(message) elif command.lower() == "ping": await cmdPing(message,bot) + elif command.lower() == "valheim": + await cmdValheim(message,bot) else: await cmdNotFound(message) @@ -71,75 +72,16 @@ async def cmdPing(message,bot): await message.channel.send(content) -async def cmdRedact(message, bot): - -# msg = await message.channel.send('This message will self destruct in 3 seconds ') -# -# await asyncio.sleep(3.0) -# await msg.edit(content='`[DATA REDACTED]`') -# await asyncio.sleep(3.0) -# await msg.edit(content='`[████ ████████]`') -# await asyncio.sleep(30.0) -# await msg.delete() - counter = 0 - async for msg in message.channel.history(limit=7): - - if msg.author.id == bot.user.id or msg.id == message.id: - try: - await msg.delete() - counter = counter + 1 - except: - pass - #print(msg) - await message.channel.send("Expunged %s messages" % counter) - - async def cmdHelp(message): - msg = await message.channel.send('''Thank you for contacting █████ ████████ for assistance. -Unfortunately you are not authorised to receive assistance from ███ ████ resources. -Remain Calm. + msg = await message.channel.send('''Greetings. This bot primarily serves to monitor the status of services and servers. Use the following commands to interact with me. If you _must_, the shorthand !007 will work. You are cleared for the following instructions. -> `!00718274602 morning` - a standard greeting. -> `!00718274602 redact` - this command removes classified messages from the public domain. -> `!00718274602 status` - This command details recent activities within the ███████ application. -> `!00718274602 help` - displays this message. -> `!00718274602 historical` - briefly retells a incident report from the █████ ████████ -\n at this time direct conversations are ███ monitored by ███ ████. ''' +> `!00718274602 status` - Shows the status of all servers and services being monitor +> `!00718274602 awaken`/`wake`/`awake` - Awakens the monitored servers +> `!00718274602 valheim` - activates all valheim game-servers. +\n +Note that servers and services may shutdown outside of normal "awake" hours if inactive for more than 20 minutes. ''' ) async def cmdNotFound(message): msg = await message.channel.send('''Unrecognised or unauthorised request. Try `!00718274602 help`''') - -async def cmdHistorical(message): - text = '''Did you ever ████ the ███████ of █████ ████████ The ████? -I thought not. It’s not a █████ ███ ████ would ████ you. -It’s a ████ legend. █████ ████████ was a ████ ████ of ███ ████, so powerful and so wise ██ could use the █████ to █████████ the █████████████ to create ████… -██ had such a knowledge of ███ ████ ████ that ██ could even keep the ones ██ cared about from █████. -███████████████████████████████████████████████████████████████████████████████████████. -██ became so powerful… the only thing ██ was ██████ of was losing ███ power, which eventually, of course, ██ ███. -Unfortunately, ██ ██████ ███ ██████████ everything ██ knew, then ███ apprentice killed ███ in ███ █████. -Ironic. ██ could ████ others from █████, but not ███████.''' - msg = await message.channel.send(text) - await asyncio.sleep(10.0) - await msg.delete() - -async def cmdMorning(message): - embed = discord.Embed() - embed.title = "Greeting" - embed.set_footer(text="NOTE: This greeting does not imply exceptional familiarity.") - additionalcomments = ["Weather based observations","Comment regarding frequency of public transportation"] - - max = len(additionalcomments) - addtionalComment = additionalcomments[random.randint(0,max-1)] - - - embed.add_field(name="greeting value",value="basic", inline=True) - embed.add_field(name="familiarity index", value="distant",inline = True) - embed.add_field(name="additional comments",value =addtionalComment, inline = False) - - - - embed.description = "This represents a single unity of recognition and good will with the parameters listed below." - msg = await message.channel.send("Good morning %s, please find your greeting attached." % message.author.mention, embed=embed) - \ No newline at end of file diff --git a/models/serviceMonitor.py b/models/serviceMonitor.py index 6c0a34e..a54c4d8 100644 --- a/models/serviceMonitor.py +++ b/models/serviceMonitor.py @@ -18,9 +18,16 @@ class monitor: _services = [] def __init__(self): self.loadConfig() + + def getServices(self, serviceType = None): + if serviceType == None: + return self._services + returnObj = [] + for service in self._services: + if service.serviceType == serviceType: + returnObj.append(service) + return returnObj - def getServices(self): - return self._services async def getServiceListText(self): returnString = "" diff --git a/serviceMonitors/serviceComputer.py b/serviceMonitors/serviceComputer.py index 7d44819..87427a0 100644 --- a/serviceMonitors/serviceComputer.py +++ b/serviceMonitors/serviceComputer.py @@ -2,6 +2,7 @@ import subprocess from models.service import * from wakeonlan import send_magic_packet +import socket #all service requests should get a name, type in JSON, then specific config settings @@ -25,16 +26,18 @@ def __init__(self,serviceName, serviceType, host, shutdownCommand, serverMacAdd self.host = host async def checkService(self): - arg = "c" if os.name == 'posix' else "n" - response=os.system("ping -%s 1 %s" % (arg,self.host)) - - if response == 0: + + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.settimeout(3) + try: + s.connect((self.host,22)) self._statusEmoji = ":green_square:" self._statusText = "online" - else: - self._statusEmoji = ":snowflake:" + except socket.error as e: + print("INFO: couldn't connect to %s, reason %s" % (self.serviceName,e)) + self._statusEmoji = ":zzz:" self._statusText = "offline" - return + def getFriendlyName(self): return self.serviceName @@ -45,8 +48,8 @@ def getStatusEmoji(self): def getStatusText(self): return self._statusText - async def tryStartService(self ): - send_magic_packet('18-c0-d4-49-59-d0') + async def tryStartService(self): + send_magic_packet(self.serverMacAddress) def tryStopService(self): os.system(self.shutdownCommand) From 7b43656dc5a8b86a6ebbfe5dcd1103ad1d185abc Mon Sep 17 00:00:00 2001 From: C'tri Date: Sun, 14 Mar 2021 22:50:10 +0000 Subject: [PATCH 16/19] update ignore and fix handler --- .gitignore | 2 ++ bot.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index d38dfed..db03e2c 100644 --- a/.gitignore +++ b/.gitignore @@ -110,3 +110,5 @@ auth.json .vscode/launch.json services.json serviceControls/* +auth-dev.json +auth-prod.json diff --git a/bot.py b/bot.py index 378f126..0f2dfea 100644 --- a/bot.py +++ b/bot.py @@ -88,7 +88,7 @@ async def on_message(self,message): regexText = r'(!0071?8?2?7?4?6?0?2? |\/0071?8?2?7?4?6?0?2? ){1}([a-zA-Z0-9 ]*){1}(-[a-zA-z]*)?' if re.search(r'[!?/](0071?8?2?7?4?6?0?2? )?valh[ei]{2}m', message.content): - await handlers.cmdValheim(message,self.serviceMonitorInstance,self) + await handlers.cmdValheim(message,self) elif re.search(r'[!?/](0071?8?2?7?4?6?0?2? )?a?waken?', message.content): await handlers.cmdAwaken(message,self) elif re.search(regexText,message.content): From e5ad8a4c2cbdc32bf1aa232d67dfed6f1de9cb75 Mon Sep 17 00:00:00 2001 From: C'tri Date: Sun, 14 Mar 2021 22:52:13 +0000 Subject: [PATCH 17/19] properly split !wake and !valheim --- handlers.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/handlers.py b/handlers.py index de80687..946717a 100644 --- a/handlers.py +++ b/handlers.py @@ -25,13 +25,11 @@ async def cmdValheim(message,bot): if serviceController is None: message.channel.send("Unable to connect to service controller - try again in 2 seconds.") return - for service in serviceController.getServices(): + for service in serviceController.getServices(serviceType="valheim"): if isinstance(service,serviceMonitors.serviceValheim.ServiceValheim): await service.tryStartService() await message.channel.send("Attempting to activate %s. See `!007 status` for server status." % service.getFriendlyName()) - elif isinstance (service,serviceMonitors.serviceComputer.ServiceComputer): - await service.tryStartService() - await message.channel.send("Sending awake instruction to %s. NOTE: run this again once it's woken up!") + async def cmdAwaken(message, bot): computerServices = bot.serviceMonitorInstance.getServices(serviceType = 'server') for service in computerServices: From c696bc779bd0b8958f4145fd1e6cf4a4afe2c2da Mon Sep 17 00:00:00 2001 From: C'tri Date: Mon, 15 Mar 2021 00:28:04 +0000 Subject: [PATCH 18/19] presence & handler improvement now gives !007 help in "listening to" activity reworded help messages server going to sleep will zero the "time since last occupied" --- bot.py | 6 ++++-- handlers.py | 9 +++++++-- serviceMonitors/serviceValheim.py | 3 +++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/bot.py b/bot.py index 0f2dfea..827c7f6 100644 --- a/bot.py +++ b/bot.py @@ -91,6 +91,9 @@ async def on_message(self,message): await handlers.cmdValheim(message,self) elif re.search(r'[!?/](0071?8?2?7?4?6?0?2? )?a?waken?', message.content): await handlers.cmdAwaken(message,self) + elif re.search(r'[!?/](0071?8?2?7?4?6?0?2? )?status', message.content): + await handlers.cmdStatus(message,self) + elif re.search(regexText,message.content): pieces = re.split(regexText,message.content) await handlers.cmdGeneral(message,pieces,self) @@ -115,8 +118,7 @@ async def on_ready(self): print(self.user.name) print(self.user.id) print('------') - - + await self.change_presence(activity=discord.Activity(type=discord.ActivityType.listening, name="!007 help")) self.serviceMonitorInstance = await serviceMonitor.getActiveMonitor() self.statusUpdaterLoop = threading.Thread(target=self.serviceUpdaterLoop) self.statusUpdaterLoop.start() diff --git a/handlers.py b/handlers.py index 946717a..b24de0a 100644 --- a/handlers.py +++ b/handlers.py @@ -25,10 +25,15 @@ async def cmdValheim(message,bot): if serviceController is None: message.channel.send("Unable to connect to service controller - try again in 2 seconds.") return + foundServices = False for service in serviceController.getServices(serviceType="valheim"): if isinstance(service,serviceMonitors.serviceValheim.ServiceValheim): await service.tryStartService() - await message.channel.send("Attempting to activate %s. See `!007 status` for server status." % service.getFriendlyName()) + foundServices = True + if foundServices == True: + await message.channel.send("Attempted to activate all valheim servers. See `!007 status` for progress.") + else: + await message.channel.send("No valheim services configured, have the administrator check `services.json`") async def cmdAwaken(message, bot): computerServices = bot.serviceMonitorInstance.getServices(serviceType = 'server') @@ -71,7 +76,7 @@ async def cmdPing(message,bot): async def cmdHelp(message): - msg = await message.channel.send('''Greetings. This bot primarily serves to monitor the status of services and servers. Use the following commands to interact with me. If you _must_, the shorthand !007 will work. + msg = await message.channel.send('''Greetings. This bot primarily serves to monitor the status of services and servers. Use the following commands to interact with me. If you _must_, the shorthand `!007` will work. You are cleared for the following instructions. > `!00718274602 status` - Shows the status of all servers and services being monitor diff --git a/serviceMonitors/serviceValheim.py b/serviceMonitors/serviceValheim.py index f1a0bdc..73a2135 100644 --- a/serviceMonitors/serviceValheim.py +++ b/serviceMonitors/serviceValheim.py @@ -40,6 +40,7 @@ async def checkService(self): except Exception as e: self._statusEmoji = ":red_square:" self._statusText = "Server unreachable" + self.lastOccupied = None print(e) return @@ -57,6 +58,8 @@ async def checkService(self): else: self._statusEmoji = ":yellow_square:" self._statusText = "Probably waking up" + self.lastOccupied = datetime.datetime.now() + if playerCount > 0: self.lastOccupied = datetime.datetime.now() else: From 6c304af34314aa72ea736832736e0caf0748ba1a Mon Sep 17 00:00:00 2001 From: C'tri Date: Mon, 15 Mar 2021 00:35:58 +0000 Subject: [PATCH 19/19] commands must now be at start of line. --- bot.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bot.py b/bot.py index 827c7f6..cd619f5 100644 --- a/bot.py +++ b/bot.py @@ -86,19 +86,19 @@ async def on_message(self,message): if message.author.id != selfID: #recursion check. do not react to your own messages - regexText = r'(!0071?8?2?7?4?6?0?2? |\/0071?8?2?7?4?6?0?2? ){1}([a-zA-Z0-9 ]*){1}(-[a-zA-z]*)?' - if re.search(r'[!?/](0071?8?2?7?4?6?0?2? )?valh[ei]{2}m', message.content): + regexText = r'^(!0071?8?2?7?4?6?0?2? |\/0071?8?2?7?4?6?0?2? ){1}([a-zA-Z0-9 ]*){1}(-[a-zA-z]*)?' + if re.search(r'^[!?\/](0071?8?2?7?4?6?0?2? )?valh[ei]{2}m', message.content): await handlers.cmdValheim(message,self) - elif re.search(r'[!?/](0071?8?2?7?4?6?0?2? )?a?waken?', message.content): + elif re.search(r'^[!?\/](0071?8?2?7?4?6?0?2? )?a?waken?', message.content): await handlers.cmdAwaken(message,self) - elif re.search(r'[!?/](0071?8?2?7?4?6?0?2? )?status', message.content): + elif re.search(r'^[!?\/](0071?8?2?7?4?6?0?2? )?status', message.content): await handlers.cmdStatus(message,self) elif re.search(regexText,message.content): pieces = re.split(regexText,message.content) await handlers.cmdGeneral(message,pieces,self) elif message.guild is None: - regexText = r'([a-zA-Z0-9 ]*){1}(-[a-zA-z]*)?' + regexText = r'^([a-zA-Z0-9 ]*){1}(-[a-zA-z]*)?' if re.search(regexText,message.content): pieces = re.split(regexText,message.content) pieces = ['','',pieces[1],pieces[2], '']