cogs, shardable, setup, update, say_ralf commands

Signed-off-by: Marc Ahlgrim <marc@onemarcfifty.com>
This commit is contained in:
Marc Ahlgrim
2022-07-26 11:43:33 +02:00
parent 2e25186e96
commit 455da6a9f3
17 changed files with 503 additions and 363 deletions
+39 -8
View File
@@ -2,16 +2,12 @@
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.4):
- reply with "pong" to a "ping" (WHOA!!!)
- 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)
- remind subscribers of upcoming events
Planned (release 0.3)
- Help the user create a support thread with a modal view
- let the user subscribe / unsubscribe to notification messages with a modal view
## How to use
@@ -22,10 +18,45 @@ You need the discord.py wrapper from Rapptz :
cd discord.py/
python3 -m pip install -U .[voice]
Next, adapt the `secret.py` file to reflect your token etc.
Also, customize all settings in `config.py` and the text files in the
`bot_messages`directory
Next, adapt the `config.json` file to reflect your token etc.
Now you can cd into the bot's directory and launch it with
python3 main.py
## slash commands
The bot supports the following commands:
**/setup** set up basic functionality (channel for idle messages, frequency of the messages, channel that contains the templates)
**/subscribe** let a user subscribe to roles that are defined in the scheduled events
**/update** update the bot (re-read the template channel)
**/say_ralf** let ralf say something. You can specify the channel and the message ;-)
## How do you configure R.A.L.F. ?
First you need a minimum `config.json`like shown in `minimum.config.json` that contains your token and client id.
All other values are taken from a "Template" channel. When you run the **/setup** command, R.A.L.F. will let you chose that one.
In that template channel, you just create messages that you want R.A.L.F. to send randomly into the configured idle_channel.
### how to configure Events ?
A special type of template message contains a definition of scheduled events. The message needs to contain data that can be converted to a dict object, like this:
{
"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": 12345452435,
"notify_hint": "get notified when the PM session starts",
"subscription_role_num": 12432345423,
"notify_minutes": 30,
"day_of_week": 6,
"start_time": "18:00:00",
"end_time": "19:00:00"
}
(when you use the /update command, it will tell you if it could read it or not.)
Every Monday, R.A.L.F. will then go through the Events and create them as scheduled events on the guild/server.
If the user has the `subscription_role_num` role, then he/she will be notified by R.A.L.F. roughly `notify_minutes`before the event starts.
-14
View File
@@ -1,14 +0,0 @@
Hi, I am R.A.L.F., your assistant bot.
Did you know that there is a **video/voice session** that is **open to everyone** on this server?
***Every Sunday at 9 AM and 6 PM Berlin/Paris time*** you can join in the <#758271650688008202> channel.
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...
****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
@@ -1,6 +0,0 @@
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
-5
View File
@@ -1,5 +0,0 @@
Hi, this is R.A.L.F., your assistant bot.
Please check in every now and then to the <#954732247909552149> channel.
This is where folks here on the server ask for help.
Maybe **** YOU **** can help someone there ?
-3
View File
@@ -1,3 +0,0 @@
Hi, I am R.A.L.F., your assistant bot.
Did you know that you can see ***all of Marc's videos*** in the <#792662116456726538> channel?
-9
View File
@@ -1,9 +0,0 @@
Hi, I am R.A.L.F., your assistant bot.
**We are always looking for Sponsors!**
If you have Nitro or if you want to spend some $$$
*** maybe you want to boost the server ? ***
This will give everyone better video and voice quality in the Sunday Funday calls or for tech support calls.
-9
View File
@@ -1,9 +0,0 @@
Hi, I am R.A.L.F., your assistant bot!
Did you know that we have a ***support channel*** here ?
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.
Please do also check in every now and then to the <#866779182293057566> channel.
Maybe ****YOU**** can help someone there ?
-7
View File
@@ -1,7 +0,0 @@
Hi, I am R.A.L.F., the Responsive Artificial Life Form ;-)
Did you know that everyone can use the
*** <#957636889718968380> ***
channel for video/voice calls at any time ?
+228 -102
View File
@@ -1,19 +1,19 @@
import discord
import random
import numpy as np
import utils
import datetime
import config
import os
import ast
from glob import glob
from dateutil import tz
from sys import exit
from discord import app_commands
from discord.ext import tasks
from discord.ext.commands import Bot,AutoShardedBot
from classes.dis_events import DiscordEvents
from classes.subscribe import Subscribe
from classes.config import Config
from dataclasses import dataclass
# #######################################
@@ -21,80 +21,222 @@ from classes.subscribe import Subscribe
# #######################################
class OMFClient(discord.Client):
class OMFBot(AutoShardedBot):
channel_idle_timer: int
lastNotifyTimeStamp = None
theGuild : discord.Guild = None
# each guild has the following elements:
# - a list of scheduled Events (EventsList)
# - a list of Messages that will be sent at idle times
# - a timer indicating the number of scheduler runs to
# wait before a new idle message is sent
lastSentMessage:discord.Message=None
@dataclass
class GuildData:
EventsList = {}
idle_messages=[]
channel_idle_timer=0
guildEventsList = None
guildEventsClass: DiscordEvents = None
# the guildDataList contains one GuildData class per item.
# the key is the guild ID
guildDataList={}
# configData is the generic config object that reads / writes
# the config data to disk
configData:Config
# EventsClass is the interface to the restful api
# that allows us to create scheduled events
EventsClass : DiscordEvents
# #######################################
# init constructor
# #######################################
def __init__(self) -> None:
print('Init')
# Try to set all intents
intents = discord.Intents.all()
super().__init__(intents=intents)
# We need a `discord.app_commands.CommandTree` instance
# to register application commands (slash commands in this case)
self.tree = app_commands.CommandTree(self)
super().__init__(command_prefix="!",intents=intents)
self.prefix="!"
self.configData=Config('config.json')
# 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()
guildNode=self.configData.readGuild(interaction.guild.id)
member = interaction.user
for option in x.Menu.options:
role = option.value
if not (member.get_role(role) is None):
option.default=True
x=Subscribe(autoEvents=guildNode["AUTO_EVENTS"],member=member)
await interaction.response.send_modal(x)
self.channel_idle_timer = 0
self.idle_channel = self.get_channel(config.CONFIG["IDLE_MESSAGE_CHANNEL_ID"])
# The setup command will ask for the guild parameters and
# read them in
@self.tree.command(name="setup", description="Define parameters for the bot")
async def setup(
interaction: discord.Interaction,
template_channel:discord.TextChannel,
idle_channel:discord.TextChannel,
idle_sleepcycles:int,
avoid_spam:int):
if not interaction.user.guild_permissions.administrator:
await interaction.response.send_message(f'only an Administrator can do that', ephemeral=True)
else:
try:
jData={
"IDLE_MESSAGE_CHANNEL_ID" : idle_channel.id,
"CONFIG_CHANNEL_ID": template_channel.id,
"CHANNEL_IDLE_INTERVAL" : idle_sleepcycles,
"AVOID_SPAM" : avoid_spam,
"AUTO_EVENTS": []
}
self.configData.writeGuild(interaction.guild.id,jData)
await interaction.response.send_message(f'All updated\nThank you for using my services!\nyou might need to run /update', ephemeral=True)
except Exception as e:
print(f"ERROR in setup command: {e}")
await interaction.response.send_message(f'Ooops, there was a glitch!', ephemeral=True)
# The update command will read the guild configs from the
# message templates channel
@self.tree.command(name="update", description="read in the message templates and update the cache")
async def update(interaction: discord.Interaction):
if not interaction.user.guild_permissions.administrator:
await interaction.response.send_message(f'only an Administrator can do that', ephemeral=True)
else:
try:
await self.readMessageTemplates(interaction.guild)
#self.configData.writeGuild(interaction.guild.id,jData)
numMessages=len(self.guildDataList[f'{interaction.guild.id}'].idle_messages)
guildNode=self.configData.readGuild(interaction.guild.id)
eventNodes=guildNode["AUTO_EVENTS"]
numEvents=len(eventNodes)
await interaction.response.send_message(f'{numEvents} Events and {numMessages} Message templates\nThank you for using my services!', ephemeral=True)
except Exception as e:
print(f"ERROR in update command: {e}")
await interaction.response.send_message(f'Ooops, there was a glitch!', ephemeral=True)
@self.tree.command(name="say_ralf", description="admin function")
async def say_ralf(
interaction: discord.Interaction,
which_channel:discord.TextChannel,
message:str):
if not interaction.user.guild_permissions.administrator:
await interaction.response.send_message(f'only an Administrator can do that', ephemeral=True)
else:
try:
await which_channel.send(message)
await interaction.response.send_message('message sent', ephemeral=True)
except Exception as e:
print(f"ERROR in say_ralf: {e}")
await interaction.response.send_message(f'Ooops, there was a glitch!', ephemeral=True)
# ################################
# the bot run command just starts
# the bot with the token from
# the json config file
# ################################
def run(self,*args, **kwargs):
super().run(token=self.configData.getToken())
# #########################
# setup_hook waits for the
# command tree to sync
# and loads the cogs
# #########################
async def setup_hook(self) -> None:
# Sync the application command with Discord.
await self.tree.sync()
# load all cogs
try:
for file in os.listdir("cogs"):
if file.endswith(".py"):
name = file[:-3]
await self.load_extension(f"cogs.{name}")
except Exception as e:
print(f"ERROR in setup_hook: {e}")
# ######################################################
# send_random_message is called when the server is idle
# and posts a random message to the server
# ######################################################
async def send_random_message(self):
async def send_random_message(self,guildID):
guildNode=self.configData.readGuild(guildID)
print("Sending random message")
if self.idle_channel == None:
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)}")
idle_channel_id=guildNode["IDLE_MESSAGE_CHANNEL_ID"]
idle_channel=self.get_channel(idle_channel_id)
gdn:self.GuildData
gdn=self.guildDataList[f'{guildID}']
idle_messages=gdn.idle_messages
# if the author of the previously last sent message and
# the new message is ourselves, then delete the
# previous message
try:
lastSentMessage = await idle_channel.fetch_message(
idle_channel.last_message_id)
if (int(f'{guildNode["AVOID_SPAM"]}') == 1) and (lastSentMessage is not None):
if (lastSentMessage.author == self.user):
await lastSentMessage.delete()
except Exception as e:
print(f"delete lastmessage error: {e}")
try:
await idle_channel.send(f'{random.choice(idle_messages)}')
except Exception as e:
print(f"send random message error: {e}")
# ######################################################
# readMessageTemplates reads all messages from
# the guildID/"config"/"CONFIG_CHANNEL_ID"] node
# of the configdata and stores it in the idleMessages dict
# in an array under the guild ID key
# ######################################################
async def readMessageTemplates(self,theGuild:discord.Guild):
# we init the guild data with a new GuildData object
self.guildDataList[f'{theGuild.id}'] = self.GuildData()
guildNode=self.configData.readGuild(theGuild.id)
if guildNode is None:
print (f"Guild {theGuild.id} has no setup")
return
theTemplateChannel:discord.TextChannel
theTemplateChannel=theGuild.get_channel(int(guildNode["CONFIG_CHANNEL_ID"]))
message:discord.Message
messages = theTemplateChannel.history(limit=50)
self.guildDataList[f'{theGuild.id}'].idle_messages=[]
eventNodes=[]
async for message in messages:
messageContent:str
messageContent=message.content
try:
someDict=ast.literal_eval(messageContent)
if isinstance(someDict, dict):
eventNodes.append(someDict)
except Exception as e:
self.guildDataList[f'{theGuild.id}'].idle_messages.append(message.content)
guildNode["AUTO_EVENTS"]=eventNodes
self.configData.writeGuild(theGuild.id,guildNode)
numMessages=len(self.guildDataList[f'{theGuild.id}'].idle_messages)
numEvents=len(eventNodes)
print(f'{numEvents} Events and {numMessages} Message templates')
# ######################################################
# on_ready is called once the client is initialized
@@ -105,37 +247,18 @@ class OMFClient(discord.Client):
async def on_ready(self):
print('Logged on as', self.user)
self.EventsClass = DiscordEvents(
discord_token=self.configData.getToken(),
client_id=self.configData.getClientID(),
bot_permissions=8,
api_version=10)
# read in the random message files
# the idle_messages array holds one element per message
# every file is read in as a whole into one element of the array
# every message is read in as a whole into one element of the array
self.idle_messages = []
for filename in glob(config.CONFIG["IDLE_MESSAGE_DIR"] + '/*.txt'):
print ("read {}",filename)
with open(filename) as f:
self.idle_messages.append(f.read())
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"]
)
for theGuild in self.guilds:
await self.readMessageTemplates(theGuild)
# start the schedulers
@@ -149,11 +272,14 @@ class OMFClient(discord.Client):
# for the next sunday
# ######################################################
async def create_events (self):
async def create_events (self,theGuild):
print("Create Events")
for theEvent in config.AUTO_EVENTS:
guildNode=self.configData.readGuild(theGuild.id)
eventNodes=guildNode["AUTO_EVENTS"]
for theEvent in eventNodes:
# calculate the date of the future event
theDate:datetime.datetime = utils.onDay(datetime.date.today(),theEvent['day_of_week'])
@@ -171,16 +297,18 @@ class OMFClient(discord.Client):
# after 2 AM
strStart=theDate.strftime(f"%Y-%m-%dT{utcStartTime}")
strEnd=theDate.strftime(f"%Y-%m-%dT{utcEndTime}")
await self.guildEventsClass.create_guild_event(
await self.EventsClass.create_guild_event(
event_name=theEvent['title'],
event_description=theEvent['description'],
event_start_time=f"{strStart}",
event_end_time=f"{strEnd}",
event_metadata={},
event_privacy_level=2,
channel_id=theEvent['channel'])
channel_id=theEvent['channel'],
guild_id=theGuild.id)
# once we have created the event, we let everyone know
channel = self.get_channel(config.CONFIG["IDLE_MESSAGE_CHANNEL_ID"])
channel = theGuild.get_channel(guildNode["IDLE_MESSAGE_CHANNEL_ID"])
await channel.send(f'Hi - I have created the scheduled Event {theEvent["title"]}')
@@ -188,9 +316,10 @@ class OMFClient(discord.Client):
# get_event_list gives a list of scheduled events
# ######################################################
async def get_events_list (self):
eventList = await self.guildEventsClass.list_guild_events()
self.guildEventsList = eventList
async def get_events_list (self,theGuild: discord.Guild):
eventList = await self.EventsClass.list_guild_events(theGuild.id)
self.GuildData(self.guildDataList[f'{theGuild.id}']).EventsList = eventList
return eventList
# ######################################################
@@ -207,30 +336,16 @@ class OMFClient(discord.Client):
if message.flags.ephemeral:
return
print("{} has just sent {}".format(message.author, message.content))
# if the author of the previously last sent message and
# the new message is ourselves, then delete the
# previous message
if (int(f'{config.CONFIG["AVOID_SPAM"]}') == 1) and (self.lastSentMessage is not None):
if ((message.author == self.user) and
(self.lastSentMessage.author == self. user) and
(int(f"{self.lastSentMessage.channel.id}") == (int(config.CONFIG["IDLE_MESSAGE_CHANNEL_ID"])))):
try:
await self.lastSentMessage.delete()
except Exception as e:
print(f"delete lastmessage error: {e}")
self.lastSentMessage = message
# don't respond to ourselves
if message.author == self.user:
return
# reset the idle timer if a message has been sent or received
self.channel_idle_timer = 0
self.guildDataList[f'{message.guild.id}'].channel_idle_timer=0
await self.process_commands(message)
# ######################################################
@@ -263,8 +378,9 @@ class OMFClient(discord.Client):
if datetime.date.today().weekday() == 0:
print("create events")
for theGuild in self.guilds:
try:
await self.create_events()
await self.create_events(theGuild=theGuild)
except Exception as e:
print(f"Daily Task create Events failed: {e}")
@@ -281,25 +397,35 @@ class OMFClient(discord.Client):
@tasks.loop(minutes=10)
async def task_scheduler(self):
self.channel_idle_timer += 1
print("SCHEDULE")
for theGuild in self.guilds:
guildNode=self.configData.readGuild(theGuild.id)
if guildNode is None:
print (f"Guild {theGuild.id} has no setup")
continue
gdn:self.GuildData
gdn=self.guildDataList[f'{theGuild.id}']
gdn.channel_idle_timer += 1
# #####################################
# see if we need to send a random message
# if the counter is greater than CHANNEL_IDLE_INTERVAL
# then send a random message into the idle_channel
# #####################################
try:
if self.channel_idle_timer >= config.CONFIG["CHANNEL_IDLE_INTERVAL"]:
self.channel_idle_timer = 0
await self.send_random_message()
if gdn.channel_idle_timer >= guildNode["CHANNEL_IDLE_INTERVAL"]:
gdn.channel_idle_timer = 0
await self.send_random_message(guildID=theGuild.id)
except Exception as e:
print(f"Scheduler random_message failed: {e}")
# see if we need to send out notifications for events
# The Event details are stored in config.
eventList=None
for theEvent in config.AUTO_EVENTS:
for theEvent in guildNode["AUTO_EVENTS"]:
# first let's convert the String dates to datetime:
theDate=utils.onDay(datetime.date.today(),theEvent['day_of_week'])
startTime=theEvent['start_time']
@@ -320,10 +446,10 @@ class OMFClient(discord.Client):
print("Let me check if the event is still on")
try:
if eventList == None:
eventList = await self.get_events_list()
eventList = await self.get_events_list(theGuild=theGuild)
for theScheduledEvent in eventList:
if theScheduledEvent["name"] == theEvent["title"]:
channel = self.get_channel(config.CONFIG["IDLE_MESSAGE_CHANNEL_ID"])
channel = self.get_channel(guildNode["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"]}'
await channel.send(f"{theMessageText}")
except Exception as e:
+87
View File
@@ -0,0 +1,87 @@
import json
# very rudimentary implementation of a
# generic config class using a json File
# this is OK for few clients.
# for many (>100) clients we should consider file locking
# for very many (>10000) clients we should use a database
class Config():
configFileName:str
cfg:json
def __init__(self,filename:str) -> None:
self.configFileName = filename
self.readConfig()
def readConfig(self) -> json:
try:
f = open(self.configFileName)
self.cfg = json.load(f)
f.close()
except Exception as e:
print(f"Error reading Config Data: {e}")
def writeConfig(self):
try:
with open(self.configFileName, 'w', encoding='utf-8') as f:
json.dump(self.cfg, f, ensure_ascii=False, indent=5)
f.close()
except Exception as e:
print(f"Error writing Config Data: {e}")
def getNode(self,nodeID:str):
if nodeID in self.cfg:
return self.cfg[nodeID]
else:
return None
def getToken(self):
secretNode=self.getNode("secret")
return secretNode.get('BOT_TOKEN')
def getClientID(self):
secretNode=self.getNode("secret")
return secretNode.get('CLIENT_ID')
def readGuild(self,guildID) -> json:
guildNode=self.getNode("guilds")
return guildNode.get(f"{guildID}")
def writeGuild(self,guildID,nodeData):
guildNode=self.getNode("guilds")
guildNode[f"{guildID}"]=nodeData
self.writeConfig()
# 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"
# #######################
# "guilds"
# #######################
# the guilds node contains all guild specific items, such as channel IDs
# that node will be created if the /setup command is used
# "CONFIG_CHANNEL_ID" is the ID of the channel where the
# idle message templates are located
# "IDLE_MESSAGE_CHANNEL_ID" is the ID of the channel where the
# bot posts a message about the new "ticket"
# QUESTION_SLEEPING_TIME (number)
# # Variable that indicates when the bot answers after a question has been asked
# (in scheduler cycles)
+12 -14
View File
@@ -10,24 +10,21 @@ from classes.restfulapi import DiscordAPI
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}")
# def __init__(self,
# discord_token: str,
# client_id: str,
# bot_permissions: int,
# api_version: int) -> None:
#
# super().__init__(discord_token,client_id,bot_permissions,api_version)
async def list_guild_events(self) -> list:
async def list_guild_events(self,guild_id) -> 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'
event_retrieve_url = f'{self.base_api_url}/guilds/{guild_id}/scheduled-events'
response_list = await self.get_api(event_retrieve_url)
return response_list
@@ -39,7 +36,8 @@ class DiscordEvents(DiscordAPI):
event_end_time: str,
event_metadata: dict,
event_privacy_level=2,
channel_id=None
channel_id=None,
guild_id=0
) -> None:
# Creates a guild event using the supplied arguments
@@ -50,7 +48,7 @@ class DiscordEvents(DiscordAPI):
# 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_create_url = f'{self.base_api_url}/guilds/{guild_id}/scheduled-events'
event_data = json.dumps({
'name': event_name,
'privacy_level': event_privacy_level,
+21 -13
View File
@@ -1,6 +1,8 @@
import discord
import traceback
import config
from numpy import array
from classes.config import Config
# ############################################
# the Subscribe() class is a modal ui dialog
@@ -12,24 +14,32 @@ import config
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)
def __init__(self, autoEvents : array,member:discord.Member) -> None:
super().__init__()
# create the menu items from the Event roles:
menu_options = []
for menu_option in config.AUTO_EVENTS:
menu_options.append(discord.SelectOption(label= menu_option["notify_hint"],
for menu_option in autoEvents:
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(
# define the UI dropdown menu Element
self.Menu = discord.ui.Select(
options = menu_options,
max_values=len(config.AUTO_EVENTS),
max_values=len(autoEvents),
min_values=0)
# Make sure the existing user roles are preselected
for option in self.Menu.options:
role = option.value
if not (member.get_role(role) is None):
option.default=True
# now add the child element to the form for drawing
self.add_item(self.Menu)
# #####################################
# on_submit is called when the user submits
@@ -46,7 +56,6 @@ class Subscribe(discord.ui.Modal, title='(un)subscribe to Event-Notification'):
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):
@@ -55,7 +64,6 @@ class Subscribe(discord.ui.Modal, title='(un)subscribe to Event-Notification'):
# 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):
+19
View File
@@ -0,0 +1,19 @@
import time
import os
from discord.ext import commands
class Information(commands.Cog):
def __init__(self, bot):
self.bot = bot
@commands.command()
async def ping(self, ctx):
before = time.monotonic()
before_ws = int(round(self.bot.latency * 1000, 1))
message = await ctx.send("🏓 Pong")
ping = (time.monotonic() - before) * 1000
await message.edit(content=f"🏓 Pong: {before_ws}ms | REST: {int(ping)}ms")
async def setup(bot):
await bot.add_cog(Information(bot))
-81
View File
@@ -1,81 +0,0 @@
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
# before the bot posts a generic "did you know"
# message
# IDLE_MESSAGE_DIR (path without trailing slash)
# the name of the directory where the text files are
# located which contain the messages
# which the bot will randomly send
# (1 file = 1 message)
# IDLE_MESSAGE_CHANNEL_ID
# the channel where the bot will post messages to
# QUESTION_SLEEPING_TIME (number)
# # Variable that indicates when the bot answers after a question has been asked
# (in scheduler cycles)
# #######################
# "AUTO_EVENTS"
# #######################
# The Auto Events.
# this is used in three contexts:
# 1. Automatic creation of the event
# 2. Automatic reminder of subscribed users
# 3. in the /subscribe command
# this needs to be an array of dict
+10 -10
View File
@@ -1,21 +1,19 @@
{
"secret" :
{
"secret": {
"BOT_TOKEN": "YOURBOTTOKEN",
"GUILD_ID" : "YOURGUILDID",
"CLIENT_ID" : "YOURCLIENTID",
"AVOID_SPAM" : 1
"CLIENT_ID": "YOURCLIENTID"
},
"config" :
{
"guilds": {
"123456456": {
"config": {
"CHANNEL_IDLE_INTERVAL": 4,
"IDLE_MESSAGE_DIR": "bot_messages",
"IDLE_MESSAGE_CHANNEL_ID": 12345678900,
"QUESTION_SLEEPING_TIME": 2,
"SUPPORT_CHANNEL_ID" : 1234567
"SUPPORT_CHANNEL_ID": 1234567,
"AVOID_SPAM": 1
},
"AUTO_EVENTS" :
[
"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...",
@@ -42,3 +40,5 @@
}
]
}
}
}
+2 -6
View File
@@ -1,8 +1,4 @@
import classes.bot as bot
import config
if config.cfg is None:
config.readConfig()
client = bot.OMFClient()
client.run(config.SECRETS["BOT_TOKEN"])
client = bot.OMFBot()
client.run()
+9
View File
@@ -0,0 +1,9 @@
{
"secret": {
"BOT_TOKEN": "YOURBOTTOKEN",
"CLIENT_ID": "YOURCLIENTID"
},
"guilds":
{
}
}