Release 0.3

Signed-off-by: Marc Ahlgrim <marc@onemarcfifty.com>
This commit is contained in:
Marc Ahlgrim
2022-07-22 12:50:06 +02:00
parent e58ba3a745
commit 6e35d9e064
15 changed files with 497 additions and 173 deletions
+1
View File
@@ -3,6 +3,7 @@
# Config file with bot secret # Config file with bot secret
secret.py secret.py
config.json*
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files
__pycache__/ __pycache__/
+1 -4
View File
@@ -2,15 +2,12 @@
R.A.L.F. (short for Responsive Artificial Lifeform ;-) ) is the "housekeeping" bot on the oneMarcFifty Discord Server R.A.L.F. (short for Responsive Artificial Lifeform ;-) ) is the "housekeeping" bot on the oneMarcFifty Discord Server
It can do the following things (release version 0.2): It can do the following things (release version 0.3):
- reply with "pong" to a "ping" (WHOA!!!) - reply with "pong" to a "ping" (WHOA!!!)
- Send out configurable random messages when the server is idle (like "Did you know...") - Send out configurable random messages when the server is idle (like "Did you know...")
- automatically create Events (we have Sunday video sessions at 9 AM and 6 PM) - automatically create Events (we have Sunday video sessions at 9 AM and 6 PM)
- remind subscribers of upcoming events - remind subscribers of upcoming events
Planned (release 0.3)
- Help the user create a support thread with a modal view - Help the user create a support thread with a modal view
- let the user subscribe / unsubscribe to notification messages with a modal view - let the user subscribe / unsubscribe to notification messages with a modal view
+3
View File
@@ -9,3 +9,6 @@ Talk about tech stuff with the other members
or just listen in... or just listen in...
****Everyone is invited!**** ****Everyone is invited!****
Type **/subscribe** if you want to get notified half an hour before the conference starts.
you can use the same command to unsubscribe at any time - no questions asked ;-)
+6
View File
@@ -0,0 +1,6 @@
Hi, I am R.A.L.F., your assistant bot.
You can interact with me by typing "/" and then a command:
**/support** helps you open a support thread as described in the <#954732247909552149> channel
**/subscribe** subscribe to get notified half an hour before the Sunday Funday session starts
+1 -1
View File
@@ -2,7 +2,7 @@ Hi, I am R.A.L.F., your assistant bot!
Did you know that we have a ***support channel*** here ? Did you know that we have a ***support channel*** here ?
Please see the <#954732247909552149> channel on how to get hep on a tech issue. Please see the <#954732247909552149> channel on how to get help on a tech issue.
Alternatively, you can type /support and I will help you to create one. Alternatively, you can type /support and I will help you to create one.
Please do also check in every now and then to the <#866779182293057566> channel. Please do also check in every now and then to the <#866779182293057566> channel.
+94 -41
View File
@@ -1,15 +1,21 @@
import discord import discord
from discord import app_commands
from dis_events import DiscordEvents
import random import random
from glob import glob
import numpy as np import numpy as np
import config
from secret import BOT_TOKEN, CLIENT_ID, GUILD_ID
import utils import utils
from discord.ext import tasks
import datetime import datetime
import config
from glob import glob
from dateutil import tz from dateutil import tz
from sys import exit
from discord import app_commands
from discord.ext import tasks
from classes.dis_events import DiscordEvents
from classes.support import Support
from classes.subscribe import Subscribe
# ####################################### # #######################################
# The OMFClient class # The OMFClient class
@@ -21,8 +27,11 @@ class OMFClient(discord.Client):
channel_idle_timer: int channel_idle_timer: int
asked_question = False asked_question = False
last_question: discord.Message = None last_question: discord.Message = None
guild_Events = None
lastNotifyTimeStamp = None lastNotifyTimeStamp = None
theGuild : discord.Guild = None
guildEventsList = None
guildEventsClass: DiscordEvents = None
# ####################################### # #######################################
# init constructor # init constructor
@@ -42,8 +51,39 @@ class OMFClient(discord.Client):
self.tree = app_commands.CommandTree(self) self.tree = app_commands.CommandTree(self)
# The support command will ask for a thread title and description
# and create a support thread for us
@self.tree.command(name="support", description="Create a support thread")
async def support(interaction: discord.Interaction):
x : Support
x= Support()
await interaction.response.send_modal(x)
# The subscribe command will add/remove the notification roles
# based on the scheduled events
@self.tree.command(name="subscribe", description="(un)subscribe to Events)")
async def subscribe(interaction: discord.Interaction):
# preload the menu items with the roles that the user has already
# we might move this to the init of the modal
x: Subscribe
role: discord.Role
member: discord.Member
x=Subscribe()
member = interaction.user
for option in x.Menu.options:
role = option.value
if not (member.get_role(role) is None):
option.default=True
await interaction.response.send_modal(x)
self.channel_idle_timer = 0 self.channel_idle_timer = 0
self.idle_channel = self.get_channel(config.IDLE_MESSAGE_CHANNEL_ID) self.idle_channel = self.get_channel(config.CONFIG["IDLE_MESSAGE_CHANNEL_ID"])
# ######################### # #########################
# setup_hook waits for the # setup_hook waits for the
@@ -62,15 +102,14 @@ class OMFClient(discord.Client):
async def send_random_message(self): async def send_random_message(self):
print("Sending random message") print("Sending random message")
if self.idle_channel == None: if self.idle_channel == None:
self.idle_channel = self.get_channel(config.IDLE_MESSAGE_CHANNEL_ID) self.idle_channel = self.get_channel(config.CONFIG["IDLE_MESSAGE_CHANNEL_ID"])
print (f'The idle channel is {config.CONFIG["IDLE_MESSAGE_CHANNEL_ID"]} - {self.idle_channel}')
await self.idle_channel.send(f"{random.choice(self.idle_messages)}") await self.idle_channel.send(f"{random.choice(self.idle_messages)}")
# ###################################################### # ######################################################
# on_ready is called once the client is initialized # on_ready is called once the client is initialized
# it then reads in the files in the config.IDLE_MESSAGE_DIR # it then reads in the files in the config.IDLE_MESSAGE_DIR
# directory and posts them randomly every # directory and starts the schedulers
# config.CHANNEL_IDLE_INTERVAL seconds into the
# config.IDLE_MESSAGE_CHANNEL_ID channel
# ###################################################### # ######################################################
async def on_ready(self): async def on_ready(self):
@@ -82,13 +121,32 @@ class OMFClient(discord.Client):
self.idle_messages = [] self.idle_messages = []
for filename in glob(config.IDLE_MESSAGE_DIR + '/*.txt'): for filename in glob(config.CONFIG["IDLE_MESSAGE_DIR"] + '/*.txt'):
print ("read {}",filename) print ("read {}",filename)
with open(filename) as f: with open(filename) as f:
self.idle_messages.append(f.read()) self.idle_messages.append(f.read())
self.idle_messages = np.array(self.idle_messages) self.idle_messages = np.array(self.idle_messages)
# store the guild for further use
guild: discord.Guild
for guild in self.guilds:
if (int(guild.id) == int(config.SECRETS["GUILD_ID"])):
print (f"GUILD MATCHES {guild.id}")
self.theGuild = guild
if (self.theGuild is None):
print("the guild (Server ID)could not be found - please check all config data")
exit()
self.guildEventsClass = DiscordEvents(
discord_token=config.SECRETS["BOT_TOKEN"],
client_id=config.SECRETS["CLIENT_ID"],
bot_permissions=8,
api_version=10,
guild_id=config.SECRETS["GUILD_ID"]
)
# start the schedulers # start the schedulers
self.task_scheduler.start() self.task_scheduler.start()
@@ -97,6 +155,7 @@ class OMFClient(discord.Client):
# ###################################################### # ######################################################
# handle_ping is called when a user sends ping # handle_ping is called when a user sends ping
# (case sensitive, exact phrase)
# it just replies with pong # it just replies with pong
# ###################################################### # ######################################################
@@ -112,8 +171,6 @@ class OMFClient(discord.Client):
print("Create Events") print("Create Events")
newEvent = DiscordEvents(BOT_TOKEN,f'https://discord.com/api/oauth2/authorize?client_id={CLIENT_ID}&permissions=8&scope=bot')
for theEvent in config.AUTO_EVENTS: for theEvent in config.AUTO_EVENTS:
# calculate the date of the future event # calculate the date of the future event
@@ -132,16 +189,16 @@ class OMFClient(discord.Client):
# after 2 AM # after 2 AM
strStart=theDate.strftime(f"%Y-%m-%dT{utcStartTime}") strStart=theDate.strftime(f"%Y-%m-%dT{utcStartTime}")
strEnd=theDate.strftime(f"%Y-%m-%dT{utcEndTime}") strEnd=theDate.strftime(f"%Y-%m-%dT{utcEndTime}")
await newEvent.create_guild_event( await self.guildEventsClass.create_guild_event(
GUILD_ID, event_name=theEvent['title'],
theEvent['title'], event_description=theEvent['description'],
theEvent['description'], event_start_time=f"{strStart}",
f"{strStart}", event_end_time=f"{strEnd}",
f"{strEnd}", event_metadata={},
{},2,theEvent['channel']) event_privacy_level=2,
channel_id=theEvent['channel'])
# once we have created the event, we let everyone know # once we have created the event, we let everyone know
channel = self.get_channel(config.IDLE_MESSAGE_CHANNEL_ID) channel = self.get_channel(config.CONFIG["IDLE_MESSAGE_CHANNEL_ID"])
await channel.send(f'Hi - I have created the scheduled Event {theEvent["title"]}') await channel.send(f'Hi - I have created the scheduled Event {theEvent["title"]}')
@@ -150,13 +207,10 @@ class OMFClient(discord.Client):
# ###################################################### # ######################################################
async def get_events_list (self): async def get_events_list (self):
newEvent = DiscordEvents(BOT_TOKEN,f'https://discord.com/api/oauth2/authorize?client_id={CLIENT_ID}&permissions=8&scope=bot') eventList = await self.guildEventsClass.list_guild_events()
eventList = await newEvent.list_guild_events(GUILD_ID) self.guildEventsList = eventList
self.guild_Events = eventList
return eventList return eventList
# ###################################################### # ######################################################
# on_message scans for message contents and takes # on_message scans for message contents and takes
# corresponding actions. # corresponding actions.
@@ -171,15 +225,15 @@ class OMFClient(discord.Client):
# don't respond to ourselves # don't respond to ourselves
if message.author == self.user: if message.author == self.user:
return return
# reset the idle timer if a message has been sent or received # reset the idle timer if a message has been sent or received
self.channel_idle_timer = 0 self.channel_idle_timer = 0
# reply to ping # reply to ping
if message.content == 'ping': if message.content == 'ping':
await self.handle_ping(message) await self.handle_ping(message)
# check if there is a question
# check if there is a question
if "?" in message.content: if "?" in message.content:
self.asked_question = True self.asked_question = True
self.last_question = message self.last_question = message
@@ -187,14 +241,14 @@ class OMFClient(discord.Client):
self.asked_question = False self.asked_question = False
self.last_question = None self.last_question = None
# ###################################################### # ######################################################
# on_typing detects if a user types. # on_typing detects if a user types.
# We might use this one day to have users agree to policies etc. # We might use this one day to have users agree to policies etc.
# before they are allowed to speak # before they are allowed to speak
# or we might launch the Support() Modal if a user starts
# to type in the support channel
# ###################################################### # ######################################################
async def on_typing(self, channel, user, _): async def on_typing(self, channel, user, _):
# we do not want the bot to reply to itself # we do not want the bot to reply to itself
if user.id == self.user.id: if user.id == self.user.id:
@@ -212,8 +266,8 @@ class OMFClient(discord.Client):
async def daily_tasks(self): async def daily_tasks(self):
print("DAILY TASKS") print("DAILY TASKS")
# Every Monday we want to create the scheduled events # Every Monday (weekday 0) we want to create the
# for the next Sunday # scheduled events for the next Sunday
if datetime.date.today().weekday() == 0: if datetime.date.today().weekday() == 0:
print("create events") print("create events")
@@ -232,7 +286,7 @@ class OMFClient(discord.Client):
# ###################################################### # ######################################################
@tasks.loop(minutes=10) @tasks.loop(seconds=10)
async def task_scheduler(self): async def task_scheduler(self):
self.channel_idle_timer += 1 self.channel_idle_timer += 1
@@ -248,7 +302,7 @@ class OMFClient(discord.Client):
# TODO - we need to send out a message to the @here role # TODO - we need to send out a message to the @here role
# asking users to help if the question had not been answered # asking users to help if the question had not been answered
# Also - if the message is a reply then we should not post into the channel # Also - if the message is a reply then we should not post into the channel
if self.channel_idle_timer > config.QUESTION_SLEEPING_TIME: if self.channel_idle_timer > config.CONFIG["QUESTION_SLEEPING_TIME"]:
print("QUESTION WITHOUT REPLY") print("QUESTION WITHOUT REPLY")
self.asked_question = False self.asked_question = False
except Exception as e: except Exception as e:
@@ -260,7 +314,7 @@ class OMFClient(discord.Client):
# then send a random message into the idle_channel # then send a random message into the idle_channel
# ##################################### # #####################################
try: try:
if self.channel_idle_timer >= config.CHANNEL_IDLE_INTERVAL: if self.channel_idle_timer >= config.CONFIG["CHANNEL_IDLE_INTERVAL"]:
self.channel_idle_timer = 0 self.channel_idle_timer = 0
await self.send_random_message() await self.send_random_message()
except Exception as e: except Exception as e:
@@ -290,11 +344,10 @@ class OMFClient(discord.Client):
print("Let me check if the event is still on") print("Let me check if the event is still on")
try: try:
if eventList == None: if eventList == None:
newEvent = DiscordEvents(BOT_TOKEN,f'https://discord.com/api/oauth2/authorize?client_id={CLIENT_ID}&permissions=8&scope=bot') eventList = await self.get_events_list()
eventList = await newEvent.list_guild_events(GUILD_ID)
for theScheduledEvent in eventList: for theScheduledEvent in eventList:
if theScheduledEvent["name"] == theEvent["title"]: if theScheduledEvent["name"] == theEvent["title"]:
channel = self.get_channel(config.IDLE_MESSAGE_CHANNEL_ID) channel = self.get_channel(config.CONFIG["IDLE_MESSAGE_CHANNEL_ID"])
theMessageText=f'Hi <@&{theEvent["subscription_role_num"]}>, the event *** {theEvent["title"]} *** will start in roughly {theEvent["notify_minutes"]} minutes in the <#{theEvent["channel"]}> channel. {theEvent["description"]}' theMessageText=f'Hi <@&{theEvent["subscription_role_num"]}>, the event *** {theEvent["title"]} *** will start in roughly {theEvent["notify_minutes"]} minutes in the <#{theEvent["channel"]}> channel. {theEvent["description"]}'
await channel.send(f"{theMessageText}") await channel.send(f"{theMessageText}")
except Exception as e: except Exception as e:
+67
View File
@@ -0,0 +1,67 @@
import json
from classes.restfulapi import DiscordAPI
# #########################################################
# The class for Discord scheduled events
# as of 2022-07, discord.py does not support
# scheduled Events. Therefore we need to use the Rest API
# #########################################################
class DiscordEvents(DiscordAPI):
def __init__(self,
discord_token: str,
client_id: str,
bot_permissions: int,
api_version: int,
guild_id:str) -> None:
super().__init__(discord_token,client_id,bot_permissions,api_version)
self.guild_id = guild_id
print (f" DiscordEvent Client {client_id} guild {guild_id}")
async def list_guild_events(self) -> list:
# Returns a list of upcoming events for the supplied guild ID
# Format of return is a list of one dictionary per event containing information.
event_retrieve_url = f'{self.base_api_url}/guilds/{self.guild_id}/scheduled-events'
response_list = await self.get_api(event_retrieve_url)
return response_list
async def create_guild_event(
self,
event_name: str,
event_description: str,
event_start_time: str,
event_end_time: str,
event_metadata: dict,
event_privacy_level=2,
channel_id=None
) -> None:
# Creates a guild event using the supplied arguments
# The expected event_metadata format is event_metadata={'location': 'YOUR_LOCATION_NAME'}
# We hard code Entity type to "2" which is Voice channel
# hence we need no Event Metadata
# The required time format is %Y-%m-%dT%H:%M:%S
# Event times can use UTC Offsets! - if you omit, then it will be
# undefined (i.e. UTC / GMT+0)
event_create_url = f'{self.base_api_url}/guilds/{self.guild_id}/scheduled-events'
event_data = json.dumps({
'name': event_name,
'privacy_level': event_privacy_level,
'scheduled_start_time': event_start_time,
'scheduled_end_time': event_end_time,
'description': event_description,
'channel_id': channel_id,
'entity_metadata': event_metadata,
'entity_type': 2
})
try:
await self.post_api(event_create_url,event_data)
except Exception as e:
print(f'DiscordEvents.createguildevent : {e}')
+61
View File
@@ -0,0 +1,61 @@
import json
import aiohttp
# #########################################################
# The class for Discord API integration
# this can be used if certain functions are not supported
# by discord.py
# #########################################################
class DiscordAPI:
# the init constructor stores the authentication-relevant data
# such as the token and the bot url
# guild ID, Client ID, Token, Bot permissions, API Version
def __init__(self,
discord_token: str,
client_id: str,
bot_permissions: int,
api_version: int) -> None:
self.base_api_url = f'https://discord.com/api/v{api_version}'
self.bot_url = f'https://discord.com/api/oauth2/authorize?client_id={client_id}&permissions={bot_permissions}&scope=bot'
self.auth_headers = {
'Authorization':f'Bot {discord_token}',
'User-Agent':f'DiscordBot ({self.bot_url}) Python/3.9 aiohttp/3.8.1',
'Content-Type':'application/json'
}
# get_api does an https get on the api
# it expects json data as result
async def get_api(self, api_url: str) -> list:
async with aiohttp.ClientSession(headers=self.auth_headers) as session:
try:
async with session.get(api_url) as response:
response.raise_for_status()
assert response.status == 200
response_list = json.loads(await response.read())
except Exception as e:
print(f'get_api EXCEPTION: {e}')
finally:
await session.close()
return response_list
# post_api does an https put to the api url
# it expects json data as var which it posts
async def post_api(self, api_url: str, dumped_jsondata: str) -> None:
async with aiohttp.ClientSession(headers=self.auth_headers) as session:
try:
async with session.post(api_url, data=dumped_jsondata) as response:
response.raise_for_status()
assert response.status == 200
except Exception as e:
print(f'post_api EXCEPTION: {e}')
finally:
await session.close()
+73
View File
@@ -0,0 +1,73 @@
import discord
import traceback
import config
# ############################################
# the Subscribe() class is a modal ui dialog
# that let's you select one or multiple
# roles that will be assigned to you.
# The roles are set in the config.AUTO_EVENTS
# variable
# ############################################
class Subscribe(discord.ui.Modal, title='(un)subscribe to Event-Notification'):
# We need to make sure that the config is read at object definition time
# because AUTO_EVENTS might be empty otherwise
if config.AUTO_EVENTS == []:
config.readConfig()
# define the menu options (label=text, vaue=role_id)
menu_options = []
for menu_option in config.AUTO_EVENTS:
menu_options.append(discord.SelectOption(label= menu_option["notify_hint"],
value=menu_option["subscription_role_num"]))
# define the UI dropdown menu Element
Menu = discord.ui.Select(
options = menu_options,
max_values=len(config.AUTO_EVENTS),
min_values=0)
# #####################################
# on_submit is called when the user submits
# the modal. It will then go through
# the menu options and revoke / assign
# the corresponding roles
# #####################################
async def on_submit(self, interaction: discord.Interaction):
role: discord.Role
roles = interaction.user.guild.roles
member: discord.Member
member = interaction.user
# first we remove all roles
for option in self.Menu.options:
role=member.get_role(option.value)
if not (role is None):
await member.remove_roles(role)
# then we assign all selected roles
# unfortunately we need to loop through all rolles
# maybe there is a better solution ?
for option in self.Menu._selected_values:
for role in roles:
if int(option) == int(role.id):
await member.add_roles(role)
await interaction.response.send_message(f'Thanks for using my services !', ephemeral=True)
async def on_error(self, interaction: discord.Interaction, error: Exception) -> None:
await interaction.response.send_message('Oops! Something went wrong.', ephemeral=True)
# Make sure we know what the error actually is
traceback.print_tb(error.__traceback__)
+79
View File
@@ -0,0 +1,79 @@
import discord
import traceback
import config
# ############################################
# the Support() class is a modal ui dialog
# that helps you create a thread in a
# selected channel. It asks for a title
# and a description and then creates
# a Thread in the config.CONFIG["SUPPORT_CHANNEL_ID"]
# channel. It also sends a message to the
# config.CONFIG["IDLE_MESSAGE_CHANNEL_ID"]
# in order to notify everyone that a
# new support message has been created
# ############################################
class Support(discord.ui.Modal, title='Open a support thread'):
# This will be a short input, where the user can enter a title
# for the new thread
theTitle = discord.ui.TextInput(
label='Title',
placeholder='a catchy title for the issue',
)
# This is a longer, paragraph style input, where user can submit
# a description of the problem
theDescription = discord.ui.TextInput(
label='Describe the problem',
style=discord.TextStyle.long,
placeholder='Type in what the problem is...',
required=False,
max_length=300,
)
# ############################################
# on_submit is called when the user submits the
# Modal. This is where we create the thread
# and send all related messages
# ############################################
async def on_submit(self, interaction: discord.Interaction):
# first let's find out which channel we will create the thread in
theGuild = interaction.guild
theChannel : discord.TextChannel
theChannel = theGuild.get_channel(config.CONFIG["SUPPORT_CHANNEL_ID"])
if not (theChannel is None):
try:
# we send a message into that channel that serves as "hook" for the thread
# (if we didn't have a message to hook then the thread would be created
# as private which requires a boost level)
xMsg= await theChannel.send (f"Support Thread for <@{interaction.user.id}>")
newThread=await theChannel.create_thread(name=f"{self.theTitle.value}",message=xMsg,auto_archive_duration=1440)
# next we want to post about the new "ticket" in the IDLE_MESSAGE_CHANNEL
theChannel = theGuild.get_channel(config.CONFIG["IDLE_MESSAGE_CHANNEL_ID"])
if (not (theChannel is None)) and (not (newThread is None)):
xMsg= await theChannel.send (f'I have created a **Support Thread** on behalf of <@{interaction.user.id}> :\n\n <#{newThread.id}> in the <#{config.CONFIG["SUPPORT_CHANNEL_ID"]}>\n\nMaybe you could check in and see if **you** can help ??? \nMany thanks !')
xMsg= await newThread.send (f'<@{interaction.user.id}> describes the problem as follows: \n{self.theDescription.value} \n \n please tag the user on your reply - thank you!' )
except Exception as e:
print(f"Support Error: {e}")
# last but not least we send an ephemeral message to the user
# linking to the created thread
await interaction.response.send_message(f'Your Support Thread has been created here: <#{newThread.id}> Please check if everything is correct.\nThank you for using my services!', ephemeral=True)
async def on_error(self, interaction: discord.Interaction, error: Exception) -> None:
await interaction.response.send_message('Oops! Something went wrong.', ephemeral=True)
# Make sure we know what the error actually is
traceback.print_tb(error.__traceback__)
+62 -38
View File
@@ -1,57 +1,81 @@
import json
# Config data is stored in config.json
# see the config.json.example file and below comments
# readConfig() reads the file
cfg = None
AUTO_EVENTS = []
SECRETS = {}
CONFIG = {}
def readConfig():
try:
f = open('config.json')
global cfg
cfg = json.load(f)
#configData = json.loads(data)
f.close()
global AUTO_EVENTS
AUTO_EVENTS = cfg["AUTO_EVENTS"]
global CONFIG
CONFIG = cfg["config"]
global SECRETS
SECRETS = cfg["secret"]
except Exception as e:
print(f"Error reading Config Data: {e}")
# the config.json contains the following main nodes:
# #######################
# "secret"
# #######################
# the secret key contains the following items:
# the BOT_TOKEN is the Oauth2 token for your bot
# example: "BOT_TOKEN" : "DFHEZRERZQRJTSUPERSECRETTTOKENUTZZH"
# The GUILD_ID is the ID of your Server - in the discord client,
# right click on your server and select " copy ID" to get it
# example: "GUILD_ID" : "0236540000563456"
# The client ID can be copied from your App settings page and is needed
# to authenticate with the Discord Restful API for Event creation
# example "CLIENT_ID" : "9990236500564536"
# #######################
# "config"
# #######################
# the config node contains all generic config items, such as channel IDs
# and scheduler variables
# CHANNEL_IDLE_INTERVAL (number)
# the number of scheduler cycles that the channel needs to be idle # the number of scheduler cycles that the channel needs to be idle
# before the bot posts a generic "did you know" # before the bot posts a generic "did you know"
# message # message
CHANNEL_IDLE_INTERVAL: int = 4 # IDLE_MESSAGE_DIR (path without trailing slash)
# the name of the directory where the text files are # the name of the directory where the text files are
# located which contain the messages # located which contain the messages
# which the bot will randomly send # which the bot will randomly send
# (1 file = 1 message) # (1 file = 1 message)
IDLE_MESSAGE_DIR: str = "bot_messages" # IDLE_MESSAGE_CHANNEL_ID
# the channel where the bot will post messages to # the channel where the bot will post messages to
IDLE_MESSAGE_CHANNEL_ID = 758271650226765848 # QUESTION_SLEEPING_TIME (number)
# # Variable that indicates when the bot answers after a question has been asked
# Variable that indicates when the bot answers after a question has been asked
# (in scheduler cycles) # (in scheduler cycles)
QUESTION_SLEEPING_TIME = 2 # #######################
# "AUTO_EVENTS"
# #######################
# The Auto Events. # The Auto Events.
# this is used in three contexts: # this is used in three contexts:
# 1. Automatic creation of the event # 1. Automatic creation of the event
# 2. Automatic reminder of subscribed users # 2. Automatic reminder of subscribed users
# 3. in the /subscribe command # 3. in the /subscribe command
# this needs to be an array of dict
# ALL TIMES LOCAL TIMEZONE OF THE BOT !!!!
AUTO_EVENTS = [
{
'title':"Sunday Funday session (AM)",
'description':"Chat with Marc and the folks here on the server ! Share your screen if you want to walk through a problem. Talk about tech stuff with the other members or just listen in...",
'channel':758271650688008202,
'notify_hint':'get notified when the AM session starts',
'subscription_role':'notify_am',
'subscription_role_num':764893618066161695,
'notify_minutes':30,
'day_of_week':6,
'start_time':"09:00:00",
'end_time':"10:00:00"
},
{
'title':"Sunday Funday session (PM)",
'description':"Chat with Marc and the folks here on the server ! Share your screen if you want to walk through a problem. Talk about tech stuff with the other members or just listen in...",
'channel':758271650688008202,
'notify_hint':'get notified when the PM session starts',
'subscription_role':'notify_pm',
'subscription_role_num':769829891419537448,
'notify_minutes':30,
'day_of_week':6,
'start_time':"18:00:00",
'end_time':"19:00:00"
}
]
-76
View File
@@ -1,76 +0,0 @@
import json
import aiohttp
# #########################################################
# The class for Discord scheduled events
# as of 2022-07, discord.py does not support
# scheduled Events. Therefore we need to use the Rest API
# #########################################################
class DiscordEvents:
'''Class to create and list Discord events utilizing their API'''
def __init__(self, discord_token: str, bot_url: str) -> None:
self.base_api_url = 'https://discord.com/api/v10'
self.auth_headers = {
'Authorization':f'Bot {discord_token}',
'User-Agent':f'DiscordBot ({bot_url}) Python/3.9 aiohttp/3.8.1',
'Content-Type':'application/json'
}
async def list_guild_events(self, guild_id: str) -> list:
print("list_guild_events")
'''Returns a list of upcoming events for the supplied guild ID
Format of return is a list of one dictionary per event containing information.'''
event_retrieve_url = f'{self.base_api_url}/guilds/{guild_id}/scheduled-events'
async with aiohttp.ClientSession(headers=self.auth_headers) as session:
try:
async with session.get(event_retrieve_url) as response:
response.raise_for_status()
assert response.status == 200
response_list = json.loads(await response.read())
except Exception as e:
print(f'EXCEPTION: {e}')
finally:
await session.close()
return response_list
async def create_guild_event(
self,
guild_id: str,
event_name: str,
event_description: str,
event_start_time: str,
event_end_time: str,
event_metadata: dict,
event_privacy_level=2,
channel_id=None
) -> None:
'''Creates a guild event using the supplied arguments
The expected event_metadata format is event_metadata={'location': 'YOUR_LOCATION_NAME'}
The required time format is %Y-%m-%dT%H:%M:%S'''
print("create_guild_event")
event_create_url = f'{self.base_api_url}/guilds/{guild_id}/scheduled-events'
event_data = json.dumps({
'name': event_name,
'privacy_level': event_privacy_level,
'scheduled_start_time': event_start_time,
'scheduled_end_time': event_end_time,
'description': event_description,
'channel_id': channel_id,
'entity_metadata': event_metadata,
'entity_type': 2
})
async with aiohttp.ClientSession(headers=self.auth_headers) as session:
try:
async with session.post(event_create_url, data=event_data) as response:
response.raise_for_status()
assert response.status == 200
except Exception as e:
print(f'EXCEPTION: {e}')
finally:
await session.close()
+43
View File
@@ -0,0 +1,43 @@
{
"secret" :
{
"BOT_TOKEN" : "YOURBOTTOKEN",
"GUILD_ID" : "YOURGUILDID",
"CLIENT_ID" : "YOURCLIENTID"
},
"config" :
{
"CHANNEL_IDLE_INTERVAL": 4,
"IDLE_MESSAGE_DIR" : "bot_messages",
"IDLE_MESSAGE_CHANNEL_ID" : 12345678900,
"QUESTION_SLEEPING_TIME" : 2,
"SUPPORT_CHANNEL_ID" : 1234567
},
"AUTO_EVENTS" :
[
{
"title":"Sunday Funday session (AM)",
"description":"Chat with Marc and the folks here on the server ! Share your screen if you want to walk through a problem. Talk about tech stuff with the other members or just listen in...",
"channel":12345678900,
"notify_hint":"get notified when the AM session starts",
"subscription_role":"notify_am",
"subscription_role_num":12345678900,
"notify_minutes":30,
"day_of_week":6,
"start_time":"09:00:00",
"end_time":"10:00:00"
},
{
"title":"Sunday Funday session (PM)",
"description":"Chat with Marc and the folks here on the server ! Share your screen if you want to walk through a problem. Talk about tech stuff with the other members or just listen in...",
"channel":12345678900,
"notify_hint":"get notified when the PM session starts",
"subscription_role":"notify_pm",
"subscription_role_num":12345678900,
"notify_minutes":30,
"day_of_week":6,
"start_time":"18:00:00",
"end_time":"19:00:00"
}
]
}
+6 -3
View File
@@ -1,5 +1,8 @@
import bot import classes.bot as bot
import secret import config
if config.cfg is None:
config.readConfig()
client = bot.OMFClient() client = bot.OMFClient()
client.run(secret.BOT_TOKEN) client.run(config.SECRETS["BOT_TOKEN"])
-10
View File
@@ -3,22 +3,12 @@ import datetime
# ################################################ # ################################################
# Returns the date of the next given weekday after # Returns the date of the next given weekday after
# the given date. For example, the date of next Monday. # the given date. For example, the date of next Monday.
#
# NB: if it IS the day we're looking for, this returns 0. # NB: if it IS the day we're looking for, this returns 0.
# consider then doing onDay(foo, day + 1). # consider then doing onDay(foo, day + 1).
#
# Monday=0, Tuesday=1 .... Sunday=6 # Monday=0, Tuesday=1 .... Sunday=6
# ################################################ # ################################################
def onDay(date, day): def onDay(date, day):
"""
Returns the date of the next given weekday after
the given date. For example, the date of next Monday.
NB: if it IS the day we're looking for, this returns 0.
consider then doing onDay(foo, day + 1).
"""
days = (day - date.weekday() + 7) % 7 days = (day - date.weekday() + 7) % 7
return date + datetime.timedelta(days=days) return date + datetime.timedelta(days=days)