Files
r0b3BOT/r0b3bot.py
T
2020-04-14 21:59:10 -04:00

941 lines
39 KiB
Python

#!/usr/bin/python3
import discord
from discord.ext import commands
import urllib.request
import random
import os
import sys
import octoapi
import time
from datetime import datetime
import configparser
import json
from discord import FFmpegPCMAudio
from discord.utils import get
from discord.voice_client import VoiceClient
import requests
import asyncio
#from aiofile import AIOFile
import aiofiles as aiof
import pickle
#Record the time the bot started
start_time = time.time()
config = configparser.RawConfigParser()
configFilePath = r'bot_config.conf'
# Last error message variable used by $last_error command to display in discord
global last_error
last_error = "No errors have been recorded."
try:
config.read(configFilePath)
except:
print("Error loading config! Please check config file 'bot-config.conf'")
#
# Read in bot configurations
#
# Setup HomeAssistant URL and Headers
HASS_TOKEN = config.get('bot-config', 'hass_token')
HASS_URL = config.get('bot-config', 'hass_url')
HASS_LIGHT = config.get('bot-config', 'hass_light')
hassURL = HASS_URL + "/api/states/" + HASS_LIGHT
hassHEADERS = {
'Authorization': f"Bearer {HASS_TOKEN}",
'content-type': 'application/json',
}
OCTOPRINT_IP_ADDRESS = config.get('bot-config', 'octoprint_ip_address')
DISCORD_AUTH_TOKEN = config.get('bot-config', 'discord_auth_token')
BOT_COMMAND_PREFIX = config.get('bot-config', 'bot_command_prefix')
STATPING_URL = config.get('bot-config', 'statping_url')
STATPING_API_KEY = config.get('bot-config', 'statping_api_key')
statpingURL = STATPING_URL + "/api/services"
statpingHEADERS = {
'Authorization': f"Bearer {STATPING_API_KEY}",
'content-type': 'application/json',
}
# Configure bot
bot = commands.Bot(command_prefix=BOT_COMMAND_PREFIX, description='A derpy derp of a bot.')
# Printing configuration details to console
print(f"HomeAssistant URL: {hassURL}")
print(f"HomeAssistant URL HEADERS: {hassHEADERS}")
print(f"HomeAssistant Light: {HASS_LIGHT}")
print(f"OctoPrint IP Address: {OCTOPRINT_IP_ADDRESS}")
print(f"StatPing Server: {STATPING_URL}")
print(f"StatPing API Key: {STATPING_API_KEY}")
print(f"StatPing URL Headers: {statpingHEADERS}")
# On Ready
@bot.event
async def on_ready():
print('Logged in as')
print(bot.user.name)
print(bot.user.id)
print('------')
print("Setting activity to 'Listenting to your commands'")
activity = discord.Activity(name="your $commands",type=discord.ActivityType.listening)
await bot.change_presence(activity=activity)
# If 15 seconds or less have passed since the program was started, start the monitor.
runtime = time.time() - start_time
if runtime <= 15:
#Run StatPing_Monitor
print("Starting StatPing Monitor.")
await StatPing_Monitor()
else:
print(f">> Bot has been running for more than {int(runtime)} seconds. Not restarting monitor.")
@bot.command()
async def greetings(ctx):
await ctx.send(":smiley: :wave: Hello there!")
@bot.event
async def on_command_error(ctx, error):
global last_error
if isinstance(error, commands.CommandOnCooldown):
seconds = round(error.retry_after,1)
print(f" - Error, user tried to use command while in cooldown (wait is: {seconds}")
await ctx.send(f"This command is ratelimited per user, please try again in {seconds}s")
if isinstance(error, commands.CommandInvokeError):
print(" - ERROR: encountered 'CommandInvokeError'")
print(f" - Dumping error: {error}")
last_error = error
await ctx.send("Error, unable to complete your request.")
else:
print('Oopsie, I found an error...')
print(f"Error: {error}")
last_error = error
#
# Debug commands
#
@bot.command()
async def last_error(ctx):
await ctx.send(f"Last error recorded: {last_error}")
#
# Giphy Commands
#
@bot.command()
async def cat(ctx):
await ctx.send("https://media.giphy.com/media/JIX9t2j0ZTN9S/giphy.gif")
@bot.command()
async def explain(ctx):
await ctx.send("https://media.giphy.com/media/A8NNZlVuA1LoY/giphy.gif")
@bot.command()
async def holyshit(ctx):
await ctx.send("https://media.giphy.com/media/QcGTswMKyBNgA/giphy.gif")
###
#
# Image Commands
#
@bot.command()
async def smudge(ctx):
print(" - Uploading Smudge to chat channel")
file = discord.File("./images/smudge.png", filename="smudge.png")
await ctx.send(file=file)
#
# Voice Commands
#
@bot.command()
@commands.cooldown(rate=1, per=30.0, type=commands.BucketType.user)
async def bitch(ctx, member : discord.Member="NONE"):
datestring = datetime.now()
datestring = datestring.strftime("%m/%d/%Y-%H:%M:%S")
if ctx.message.channel.is_nsfw():
await play_sound(ctx, member, "./sounds/monkey_bitch.mp3", "$bitch", 5)
else:
print(f"[{datestring}]: {ctx.message.author.display_name} called $bitch, but is not in a NSFW channel")
await ctx.send("This command is too explicit for you!")
@bot.command()
@commands.cooldown(rate=1, per=10.0, type=commands.BucketType.user)
async def cowbell(ctx, member : discord.Member="NONE"):
await play_sound(ctx, member, "./sounds/More_cowbell.mp3", "$cowbell", 5)
@bot.command()
@commands.cooldown(rate=1, per=10.0, type=commands.BucketType.user)
async def boom(ctx, member : discord.Member="NONE"):
datestring = datetime.now()
datestring = datestring.strftime("%m/%d/%Y-%H:%M:%S")
if ctx.message.channel.is_nsfw():
await play_sound(ctx, member, "./sounds/BoomBitch.mp3", "$boom", 5)
else:
print(f"[{datestring}]: {ctx.message.author.display_name} called $boom, but is not in a NSFW channel")
await ctx.send("This command is too explicit for you!")
@bot.command()
@commands.cooldown(rate=1, per=10.0, type=commands.BucketType.user)
async def oops(ctx, member : discord.Member="NONE"):
await play_sound(ctx, member, "./sounds/Oops.mp3", "$oops", 5)
@bot.command()
@commands.cooldown(rate=1, per=10.0, type=commands.BucketType.user)
async def trololo(ctx, member : discord.Member="NONE"):
await play_sound(ctx, member, "./sounds/Trololo.mp3", "$trololo", 5)
@bot.command()
@commands.cooldown(rate=1, per=10.0, type=commands.BucketType.user)
async def leeroy(ctx, member : discord.Member="NONE"):
await play_sound(ctx, member, "./sounds/leeroy.mp3", "$leeroy", 5)
@bot.command()
@commands.cooldown(rate=1, per=10.0, type=commands.BucketType.user)
async def promoted(ctx, member : discord.Member="NONE"):
await play_sound(ctx, member, "./sounds/Promoted.mp3", "$promoted", 5)
@bot.command()
@commands.cooldown(rate=1, per=10.0, type=commands.BucketType.user)
async def wow(ctx, member : discord.Member="NONE"):
await play_sound(ctx, member, "./sounds/wow.mp3", "$wow", 5)
@bot.command()
@commands.cooldown(rate=1, per=10.0, type=commands.BucketType.user)
async def aww(ctx, member : discord.Member="NONE"):
await play_sound(ctx, member, "./sounds/Awww_Bitch.mp3", "$aww", 5)
@bot.command()
@commands.cooldown(rate=1, per=10.0, type=commands.BucketType.user)
async def eia(ctx, member : discord.Member="NONE"):
await play_sound(ctx, member, "./sounds/awesome.mp3", "$eia", 11)
@bot.command()
@commands.cooldown(rate=1, per=10.0, type=commands.BucketType.user)
async def yaw(ctx, member : discord.Member="NONE"):
await play_sound(ctx, member, "./sounds/yaw.mp3", "$yaw", 6)
@bot.command()
@commands.cooldown(rate=1, per=10.0, type=commands.BucketType.user)
async def heavy(ctx, member : discord.Member="NONE"):
await play_sound(ctx, member, "./sounds/heavy.mp3", "$heavy", 6)
@bot.command()
@commands.cooldown(rate=1, per=10.0, type=commands.BucketType.user)
async def rs(ctx, member : discord.Member="NONE"):
datestring = datetime.now()
datestring = datestring.strftime("%m/%d/%Y-%H:%M:%S")
if ctx.message.channel.is_nsfw():
soundfiles = ["./sounds/leeroy.mp3","./sounds/monkey_bitch.mp3","./sounds/More_cowbell.mp3","./sounds/BoomBitch.mp3","./sounds/Promoted.mp3","./sounds/Trololo.mp3","./sounds/Oops.mp3"]
await play_sound(ctx, member, random.choice(soundfiles), "$rs")
else:
print(f"[{datestring}]: {ctx.message.author.display_name} called $rs, but is not in a NSFW channel")
await ctx.send("This command is too explicit for you!")
async def play_sound(ctx, member : discord.Member, soundFile, command, playtime):
discord.opus.load_opus("libopus.so")
datestring = datetime.now()
datestring = datestring.strftime("%m/%d/%Y-%H:%M:%S")
print(f"[{datestring}]: {ctx.message.author.display_name} called {command}")
# Need to check whether or not a user has been specified. If one has, we need to use the channel
# they are currently connected to instead the channel that the command issuer is in.
if member != "NONE":
try:
channel = member.voice.channel
except:
await ctx.send(f"{member.display_name} is not in a voice channel!")
print(f" ! {member.display_name} is not in a voice channel")
return
print(f" - User specified: {member.display_name}")
print(f" - Destination channel: {channel}")
if member == "NONE":
try:
channel = ctx.message.author.voice.channel
except:
await ctx.send("You are not connected to a voice channel.")
print("User is not connected to a voice channel")
return
voice: discord.VoiceClient = discord.utils.get(bot.voice_clients, guild=ctx.guild)
# Different join actions are needed depending on whether or not the bot is already in a
# voice channel. If already in a channel, wait until disconnected, then join the requested
# channel. If not already in a channel, then join requested channel immediately
if voice and voice.is_connected():
print(f" - Moving to channel '{channel}''")
await ctx.send(f"Moving to '{channel}' for just a moment")
await voice.move_to(channel)
else:
print(f" - Joining Channel '{channel}''")
await ctx.send(f"Joining '{channel}' for just a moment")
voice = await channel.connect()
print(" - Creating Player")
audio_source = discord.FFmpegPCMAudio(soundFile)
print(f" - Playing {soundFile}")
time.sleep(1)
voice.play(audio_source, after=None)
time.sleep(playtime)
print(f" - Disconnecting from '{channel}'")
await voice.disconnect()
###
#
# OctoPrint Commands
#
@bot.command()
async def printstat(ctx):
datestring = datetime.now()
datestring = datestring.strftime("%m/%d/%Y-%H:%M:%S")
print(f"[{datestring}]: {ctx.message.author.display_name} called $printstat")
#print("========== Command Called at %s ==========" % (time.strftime("%Y%m%d-%H%M%S")))
# We haven't yet changed the state of the light, setting this to False
turned_on_light = False
# No Data from Printer?
operational = True
# Try to get the status of the 3D printer light
print(" - Getting Printer Light Status")
work_lights = requests.get(hassURL, headers=hassHEADERS)
print(f" JSON Response from HomeAssistant: {work_lights.text}")
work_lights = json.loads(work_lights.text)
# Making sure that the API request succeeded. If it has, there will be a state attribute added to
# work_lights.
try:
work_lights["state"]
except:
print(" ! Unable to get light status from Homeassistant, notifying chat channel")
await ctx.send("Hmm, I can't tell if the light is on... oh well [Hass API error]")
# Create empty dict w/ state of unknown
work_lights = {'state': "unknown"}
# If the light is off, let's turn it on before we take a picture
if work_lights["state"] == 'off':
print(" - Lights are off, turning on for image capture")
turned_on_light = True
lightMessages = ["Woah, it's pretty dark in R0b3's Basement... Give me a couple seconds to turn on a light.", \
"Turning on a light, give me a moment...", \
"I can't see a thing, let me get a light...", \
"Where's that light switch?...", \
"Uh, the lights are off. Give me a sec...", \
"Replacing the light bulb, just a moment..."]
await ctx.send(f"{random.choice(lightMessages)}")
try:
# REST API call to home assistant to turn the light off.
hassAPIURL = HASS_URL + "/api/services/switch/turn_on"
payload = f'{{"entity_id": "{HASS_LIGHT}"}}'
#print(f" - Payload String: {payload}")
response = requests.post(hassAPIURL, headers=hassHEADERS, data=payload)
#print(f" - POST Response: {response.text}")
except Exception as e:
print(f" ! Unable to turn on light; ERROR: {e}")
await ctx.send("Sorry, I was unable to turn on the light.")
pass
time.sleep(3)
# Randomly generate a filename to save the image to
file_name = random.randrange(1,10000)
full_file_name = str(file_name) + '.jpg'
# Get an image from OctoPrint and save it
print(" - Getting image from Webcam")
urllib.request.urlretrieve("http://%s:8080/?action=snapshot" % (OCTOPRINT_IP_ADDRESS), full_file_name)
# If we turned on the light, let's be nice and turn it back off
if turned_on_light:
print(" - Turning Light back Off")
try:
# REST API call to home assistant to turn the light off.
hassAPIURL = HASS_URL + "/api/services/switch/turn_off"
payload = f'{{"entity_id": "{HASS_LIGHT}"}}'
response = requests.post(hassAPIURL, headers=hassHEADERS, data=payload)
#print(f" - POST Response: {response.text}")
except Exception as e:
print(f" ! Unable to turn on light; ERROR: {e}")
print(" ! Unable to turn off light")
pass
# Send the image to the chat channel
print(" - Uploading image to chat channel")
file = discord.File(full_file_name, filename=full_file_name)
await ctx.send("3D Printer Snapshot:", file=file)
# Remove the Image File now that it is no longer needed
print(" - Deleting temporary image file")
os.remove(full_file_name)
print(" - Connecting to OctoPrint API")
# Testing OctoPrint API Status. If printer is off, I won't receive JSON data.
try:
operational = json.loads(octoapi.get_printer_dict())
except:
# Printer is Offline
operational = False
if not operational:
print(" - 3D Printer is on and printing, getting details")
# Get the name of the file currently being printed
try:
print(" - Getting Filename")
print_filename = octoapi.get_printFileName()
except:
print_filename = ""
try:
print(" - Getting % Completion")
# Try to get the % of Completion
print_completion = round(octoapi.get_completion(), 2)
except:
print_completion = "Unknown"
# Get the current print time in seconds
try:
print(" - Getting Seconds Elapsed")
print_seconds = octoapi.get_printTime()
except:
print("Unable to get Seconds data from OctoPrint")
# Get the time remaining for current print job in seconds
try:
print(" - Getting Seconds Left")
print_secondsleft = octoapi.get_printTimeLeft()
except:
print(" ! Unable to get Seconds Left from OctoPrint")
try:
print(" - Converting Seconds to Hours")
# Try to convert seconds to hours
print_hours = int(((print_seconds / 60) / 60))
except:
print_hours = "Unknown"
try:
print(" - Converting Seconds left to Hours left")
# Try to convert seconds left to hours left
print_hoursleft = int(((print_secondsleft / 60) / 60))
except:
print_hoursleft = "Unknown"
try:
print(" - Calculating Minutes Elapsed")
# Using the total number of minutes minus the total number of whole hours to get the minutes remaining
print_min = int(((print_seconds / 60) - (print_hours * 60)))
except:
print_min = "Unknown"
try:
print(" - Calculating Minutes Left")
# Same as above but for the minutes left in print job
print_minleft = int(((print_secondsleft / 60) - (print_hoursleft * 60)))
except:
print_minleft = "Unknown"
time_elapsed = "%s Hours %s Minutes" % (print_hours, print_min)
time_remaining = "%s Hours %s Minutes" % (print_hoursleft, print_minleft)
printer_bed = "%s°C" % (octoapi.get_printer_dict()["temperature"]["bed"]["actual"])
printer_hotend = "%s°C" % (octoapi.get_printer_dict()["temperature"]["tool0"]["actual"])
embed = discord.Embed(title="R0b3's 3D Printer Status", description="Current Job: " + print_filename, color=0xF5A623)
embed.add_field(name="Percent Complete: ", value=str(print_completion) + "%")
embed.add_field(name="Time Elapsed: ", value=time_elapsed)
embed.add_field(name="Time Remaining: ", value=time_remaining)
embed.add_field(name="Bed Temp.: ", value=printer_bed)
embed.add_field(name="Hotend Temp.: ", value=printer_hotend)
print(" - Sending details to chat channel")
await ctx.send(embed=embed)
activity = discord.Activity(name=f"3D Print @ {str(print_completion)}%",type=discord.ActivityType.watching)
await bot.change_presence(activity=activity)
await updateStatus()
else:
print(" - 3D Printer is not on or is not printing")
embed = discord.Embed(title="Printer is Offline")
print(" - Notifying chat channel")
await ctx.send(embed=embed)
async def updateStatus():
# Initialize variables
print_completion = 0
unknowns = 0
#While the print status is less than 100 % complete, keep updating
while print_completion < 100:
try:
print(" - Getting % Completion")
# Try to get the % of Completion
print_completion = round(octoapi.get_completion(), 2)
except:
print_completion = 999
unknowns += 1
#Set the activity to the new percent complete value
if print_completion != 999:
activity = discord.Activity(name=f"3D Print @ {str(print_completion)}%",type=discord.ActivityType.watching)
else:
activity = discord.Activity(name=f"3D Print @ ERROR",type=discord.ActivityType.watching)
await bot.change_presence(activity=activity)
if (unknowns >= 5):
break
await asyncio.sleep(30)
# Flash the status @ 100% before changing back to listening to your commands, only if uknowns !5.
if unknowns != 5:
for x in range(20):
activity = discord.Activity(name="-",type=discord.ActivityType.watching)
await bot.change_presence(activity=activity)
await asyncio.sleep(1)
activity = discord.Activity(name=f"3D Print @ {str(print_completion)}%",type=discord.ActivityType.watching)
await bot.change_presence(activity=activity)
await asyncio.sleep(1)
print(" - Done watching 3D Printer status")
await asyncio.sleep(3)
activity = discord.Activity(name="your commands",type=discord.ActivityType.listening)
await bot.change_presence(activity=activity)
@bot.command()
async def info(ctx):
embed = discord.Embed(title="R0b3BOT", description="Derpy Derp of a Bot :P", color=0xeee657)
# give info about you here
embed.add_field(name="Author", value="R0b3")
# Shows the number of servers the bot is member of.
embed.add_field(name="Server count", value=f"{len(bot.guilds)}")
# give users a link to invite thsi bot to their server
embed.add_field(name="Invite", value="[Invite link](https://discordapp.com/api/oauth2/authorize?client_id=416312716677087233&permissions=51264&scope=bot)")
# Bot Uptime
second = time.time() - start_time
minute, second = divmod(second, 60)
hour, minute = divmod(minute, 60)
day, hour = divmod(hour, 24)
week, day = divmod(day, 7)
week = int(week)
day = int(day)
hour = int(hour)
minute = int(minute)
second = int(second)
embed.add_field(name="Uptime [WW:DD:HH:MM:SS]:", value=f"{week:02d}:{day:02d}:{hour:02d}:{minute:02d}:{second:02d}", inline=False)
print("Getting git describe data to display as version in info embed")
bot_version = await run('git describe')
embed.add_field(name="Version:", value=f"{bot_version}", inline=False)
# Gitlab link to source
embed.add_field(name="Source Code:", value="[R0b3Bot on Gitlab](https://gitlab.rickelobe.com/Bots/r0b3BOT)", inline=False)
await ctx.send(embed=embed)
bot.remove_command('help')
@bot.command()
async def spstatus(ctx, service = "NONE"):
#async def spalert(ctx, cmd = "NONE", arg = "NONE"):
# if cmd != "NONE":
# if cmd == "add":
# spalert +=
if service != "NONE":
#await get_stp_status(ctx, service.lower())
result = await get_stp_status(service.lower())
if result == "service not found":
await ctx.send(f"'{service}' was not found")
elif result['online']:
await ctx.send(f"{result['name']} is Online")
elif not result['online']:
await ctx.send(f"{result['name']} is Offline")
else:
await ctx.send(f"Received an unexpected result '{result}' querying '{service}'")
else:
await ctx.send("Please speficy a service name to query.")
@bot.command()
async def spsub(ctx, service = "NONE"):
spsublist = {} # Statping Subscription list to be read in from file
# Structure of spsublist nested dictionary
# spsublist[service name][list of channel ids][a state value]
# dict = {'Plex': {'state' : 'online', 'channels' : ['1232', '43234']}
# 'Space Eingineers' : {'state' : online', 'channels' : ['2343', '54563']}}
#
currentsub_request = [] # list of elements detailing the current sub request
datestring = datetime.now()
datestring = datestring.strftime("%m/%d/%Y-%H:%M:%S")
# replace _ with sapces
service = service.replace("_"," ")
print(f"[{datestring}]: {ctx.message.author.display_name} called '$spsub '{service}'''")
# Only work on this if the user has supplied a service name to monitor, a value of NONE
# means nothing was specified
if service != "NONE" and service != "-list" and not service.startswith('-del '):
print(f">> Querying status of '{service}' to see if it exists")
service_state = await get_stp_status(service)
if service != "NONE":
if service == "-list":
# List the serivces that this channel is subscribed to
# Read in spsublist
sublist = ''
async with aiof.open('spsublist.dat', 'rb') as datafile:
pickled_spsublist = await datafile.read()
spsublist = pickle.loads(pickled_spsublist)
for service in spsublist.keys():
for channel in spsublist[service]['channels']:
if channel == ctx.channel.id:
sublist += f"'{service}' "
await ctx.send(f"This channel is subscribed to: {sublist}")
elif service.startswith('-del:'):
# Delete the service from this channels subscriptions
service_toremove = service[5:]
print(f"{service_toremove}")
# Open the data file for read in
async with aiof.open('spsublist.dat', 'rb') as datafile:
pickled_spsublist = await datafile.read()
spsublist = pickle.loads(pickled_spsublist)
# We haven't found either the channel or servic; mark as false
found_service = False
found_channel = False
# Iterate through the service names in the data
for service in spsublist.keys():
# If the service we want to unsub from is the same as the service in the data, we need to search for the channel
if service.lower() == service_toremove.lower():
# We've found the service match, mark as true
found_service = True
# Iterate through the channels that are subbed to this service
for channel in spsublist[service]['channels']:
# If a channel listed in the service matches the current users channel id, we've found our match
if channel == ctx.channel.id:
# Marking channel as found
found_channel = True
# Get the proper name of the service
service_toremove = service
#Remove the current users channel from this service
spsublist[service]['channels'].remove(ctx.channel.id)
# Break since we're done.
break
# If we got to the point of finding the channel, we're done.
if found_channel:
break
if found_service and found_channel:
print(">> Saving dictionary to spsublist.dat")
async with aiof.open('spsublist.dat', 'wb') as datafile:
pickled_spsublist = pickle.dumps(spsublist, protocol=4)
await datafile.write(pickled_spsublist)
#await datafile.fsync()
await datafile.flush()
await ctx.send(f"This channel is unsubscribed from '{service}' alerts.")
else:
await ctx.send(f"This channel is not subscribed to alerts for '{service_toremove}''")
# If the service exists, proceed with adding it to the list.
elif service_state != "service not found":
print(f">> '{service}' is a valid service on StatPing.")
print(f">> Checking if it has already been subscribed to")
#await ctx.send(f"'{service_state['name']}' added to monitored services")
currentsub_request.append(ctx.channel.id)
currentsub_request.append(service_state['name'])
currentsub_request.append("online")
print(f">> Channel ID: {currentsub_request[0]}")
print(f">> Service Name: {currentsub_request[1]}")
print(f">> Initial State to set: {currentsub_request[2]}")
# read in data file containing dict of subscriptions
print(">> Checking for spsublist.dat")
if os.path.exists('spsublist.dat'):
print(">> Data file exists, reading it in.")
# Read in spsublist
async with aiof.open('spsublist.dat', 'rb') as datafile:
pickled_spsublist = await datafile.read()
spsublist = pickle.loads(pickled_spsublist)
print(f">> Checking if serivce is already in datafile")
found_service = False
found_channel = False
for service in spsublist.keys():
print(f">> Does {service.lower()} == {currentsub_request[1].lower()}")
if service.lower() == currentsub_request[1].lower():
found_service = True
# This service matched what the user is trying to subscribe to
# Now we need to check if this is for the same channel
print(f">> Found {service} in data file, checking for channel")
for channel in spsublist[service]['channels']:
if channel == currentsub_request[0]:
print(f">> This channel is already subscribed to {service}")
await ctx.send(f"This channel is already subscribed to {service} alerts")
found_channel = True
break
if not found_channel:
# Append the current channel id to the list for this service
print(f">> Adding serivce '{currentsub_request[1]}' to {ctx.channel.id}")
await ctx.send(f"{service} has been added to monitored services for this channel")
spsublist[service]['channels'].append(currentsub_request[0])
break
if not found_service:
# Append the current channel id to the list for this service
print(f">> Adding serivce '{currentsub_request[1]}' to {ctx.channel.id}")
await ctx.send(f"{currentsub_request[1]} has been added to monitored services for this channel")
#newsub = {}
#newsub[currentsub_request[1]] = {'state' : 'online', 'channels' : [currentsub_request[0]]}
spsublist[currentsub_request[1]] = {'state' : 'online', 'channels' : [currentsub_request[0]]}
else:
print(">> spsublist.dat does not exist, new file will be created")
# append new subscription to dict
print(">> Creating new dictionary")
spsublist[currentsub_request[1]] = {'state' : 'online', 'channels' : [currentsub_request[0]]}
print(f">> {service} subscription has been saved")
await ctx.send(f"{service} has been added to monitored services for this channel")
#Write updated array to data file
print(">> Saving dictionary to spsublist.dat")
async with aiof.open('spsublist.dat', 'wb') as datafile:
pickled_spsublist = pickle.dumps(spsublist, protocol=4)
await datafile.write(pickled_spsublist)
#await datafile.fsync()
await datafile.flush()
print(">> Done")
#await ctx.send(f"'{service_state['name']}' added to monitored services")
else:
print(f">> '{service}' does not exist, cancelling subscription")
await ctx.send(f"{service} was not found")
async def StatPing_Monitor():
# reads in "spsublist.dat" every 60 seconds then iterates
# through to check for service status changes. This function will need to check
# that "spsublist.dat" exists and is not empty. This function should be called
# in bot.on_ready
#new_spsublist = {}
# read in data file containing list of subscriptions
while True:
print("Statping Monitor: Reading in spsublist.dat")
if os.path.exists('spsublist.dat'):
async with aiof.open('spsublist.dat', 'rb') as datafile:
pickled_spsublist = await datafile.read()
spsublist = pickle.loads(pickled_spsublist)
for service in spsublist.keys():
# service used to be subscription
status = await get_stp_status(service)
print(f">> {service} Online: {status['online']} | Previous state: {spsublist[service]['state']}")
if status != 'status not found':
if status['online'] and spsublist[service]['state'] == 'online':
# nothing has changed, no alert needed
await asyncio.sleep(1)
elif not status['online'] and spsublist[service]['state'] == "offline":
# again, nothing has changed no alert needed
await asyncio.sleep(1)
else:
print(f">> Status of '{service}' has changed, notifying subscribed channels")
if status['online']:
spsublist[service]['state'] = 'online'
for channel in spsublist[service]['channels']:
ctx = bot.get_channel(channel)
print(f">> Alerting {ctx} that {service} is Online")
embed = discord.Embed(title=f"Service Alert", description=f"{service} is Online!", color=0x00ff40)
await ctx.send(embed=embed)
elif not status['online']:
spsublist[service]['state'] = 'offline'
for channel in spsublist[service]['channels']:
ctx = bot.get_channel(channel)
print(f">> Alerting {ctx} that {service} is Offline")
embed = discord.Embed(title=f"Service Alert", description=f"{service} is Offline!", color=0xff2200)
await ctx.send(embed=embed)
else:
for channel in spsublist[service]['channels']:
ctx = bot.get_channel(channel)
print(f">> Alerting {ctx} that {service} is an Unknown state")
embed = discord.Embed(title=f"Service Alert", description=f"{service} is in an unknown state!", color=0xffff00)
await ctx.send(embed=embed)
print(">> Done")
#new_spsublist.append(subscription)
# Write status changes to spsublist.dat
async with aiof.open('spsublist.dat', 'wb') as datafile:
pickled_spsublist = pickle.dumps(spsublist)
await datafile.write(pickled_spsublist)
#await datafile.fsync()
await datafile.flush()
else:
print(">> spsublist.dat does not exist, nothing to do.")
await asyncio.sleep(60)
async def sp_monitor(sublist):
# Checks the services listed in spsublist for status change
# Sublist is a list of 3 items:
# - ctx (used to send alert to channel)
# - service (the name of the service to check)
# - status string (last status of the service, either 'online' or 'offline')
ctx = sublist[0]
while True:
status = await get_stp_status(spsublist[1])
if status['online'] and spsublist[2] == "online":
# nothing has changed, no alert needed
await asyncio.sleep(1)
elif not status['online'] and spsublist[2] == "offline":
# again, nothing has changed no alert needed
await asyncio.sleep(1)
else:
print(f"Status of {status['name']}' has changed")
if status['online']:
embed = discord.Embed(title=f"{status['name']} Service Alert", description=f"{spsublist[1]} is Online!", color=0x00ff40)
await ctx.send(embed=embed)
#await ctx.send(f"{spsublist[1]} is Online!")
spsublist[2] = "online"
elif not status['online']:
embed = discord.Embed(title=f"{status['name']} Service Alert", description=f"{spsublist[1]} is Offline!", color=0xff2200)
await ctx.send(embed=embed)
#await ctx.send(f"{spsublist[1]} is Offline!")
spsublist[2] = "offline"
else:
embed = discord.Embed(title=f"{status['name']} Service Alert", description=f"{spsublist[1]} is in an unknown state!", color=0xffff00)
await ctx.send(embed=embed)
#await ctx.send(f"Unknown state for service {spsublist[1]}")
await asyncio.sleep(60)
async def get_stp_status(service):
# Get StatPing status via REST API
spservice_array = requests.get(statpingURL, headers=statpingHEADERS)
found = False
# spservice_array is json data, need to treat it as such
for spservice in spservice_array.json():
# using lower() to eliminate any case mismatch problems
if spservice['name'].lower() == service.lower():
found = True
return spservice
# Want to alert user if the service was not found
if not found:
return "service not found"
@bot.command()
async def help(ctx):
embed = discord.Embed(title="R0b3BOT", description="List of commands are:", color=0xeee657)
embed.add_field(name=f"{BOT_COMMAND_PREFIX}printstat", value="Uploads a snapshot of Rich's 3D printer and current stats", inline=False)
embed.add_field(name=f"{BOT_COMMAND_PREFIX}explain", value="Displays Dalek EXPLAIN gif", inline=False)
embed.add_field(name=f"{BOT_COMMAND_PREFIX}holyshit", value="Displays Marty McFly HOLY SHIT gif", inline=False)
embed.add_field(name=f"{BOT_COMMAND_PREFIX}greetings", value="Gives a nice greet message", inline=False)
embed.add_field(name=f"{BOT_COMMAND_PREFIX}cat", value="Gives a cute cat gif to lighten up the mood.", inline=False)
embed.add_field(name=f"{BOT_COMMAND_PREFIX}bitch @member", value="Plays clip of BigEric420 saying 'maybe you shouldn't be such a bitch.mp3'", inline=False)
embed.add_field(name=f"{BOT_COMMAND_PREFIX}boom @member", value="Plays 'Boom Bitch' sound clip", inline=False)
embed.add_field(name=f"{BOT_COMMAND_PREFIX}cowbell @member", value="Plays 'SNL More Cowbell' sound clip", inline=False)
embed.add_field(name=f"{BOT_COMMAND_PREFIX}oops @member", value="Plays 'Oops.mp3'", inline=False)
embed.add_field(name=f"{BOT_COMMAND_PREFIX}promoted @member", value="Plays Battlefield Friends 'PROMOTED!' sound clip", inline=False)
embed.add_field(name=f"{BOT_COMMAND_PREFIX}trololo @member", value="Plays a clip of Trololo song", inline=False)
embed.add_field(name=f"{BOT_COMMAND_PREFIX}leeroy @member", value="Plays Leeeerrroooyyy Jenkins clip", inline=False)
embed.add_field(name=f"{BOT_COMMAND_PREFIX}eia @member", value="Plays 'Everything is Awesome' song clip")
embed.add_field(name=f"{BOT_COMMAND_PREFIX}info", value="Gives a little info about the bot", inline=False)
embed.add_field(name=f"{BOT_COMMAND_PREFIX}help", value="Gives this message", inline=False)
embed.add_field(name=f"{BOT_COMMAND_PREFIX}last_error", value="Will display the real error message the bot has last encountered for additional debugging info")
embed.add_field(name=f"{BOT_COMMAND_PREFIX}spstatus servicename", value="Retrieves status of service from StatPing")
embed.add_field(name=f"{BOT_COMMAND_PREFIX}spsub service", value="Allows user to subscribe to service status change alerts for the channel. Can replace <service> with -list and -del:service to manage alerts. NOTICE:Replace spaces in service names w/ '_'!")
await ctx.send(embed=embed)
embed = discord.Embed(title="A note about sound clips:", description="Sound clips are only played in voice channels. If a user is not specified when calling the command, the sound will played in the channel of that issuing user is currently joined to. When a username is specified, the bot will play the sound in the channel that user is currently in.")
await ctx.send(embed=embed)
# Function to run shell command and return stdout or sterr
async def run(cmd):
proc = await asyncio.create_subprocess_shell(
cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE)
stdout, stderr = await proc.communicate()
print(f'[{cmd!r} exited with {proc.returncode}]')
if stdout:
#print(f'[stdout]\n{stdout.decode()}')
return stdout.decode()
if stderr:
#print(f'[stderr]\n{stderr.decode()}')
return stderr.decode()
bot.run(DISCORD_AUTH_TOKEN)