first commit
This commit is contained in:
+145
@@ -0,0 +1,145 @@
|
|||||||
|
# ---> Python
|
||||||
|
|
||||||
|
# Config file with bot secret
|
||||||
|
|
||||||
|
secret.py
|
||||||
|
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
share/python-wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
*.py,cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
cover/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
db.sqlite3-journal
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
.pybuilder/
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
# For a library or package, you might want to ignore these files since the code is
|
||||||
|
# intended to run in multiple environments; otherwise, check them in:
|
||||||
|
# .python-version
|
||||||
|
|
||||||
|
# pipenv
|
||||||
|
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||||
|
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||||
|
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||||
|
# install all needed dependencies.
|
||||||
|
#Pipfile.lock
|
||||||
|
|
||||||
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||||
|
__pypackages__/
|
||||||
|
|
||||||
|
# Celery stuff
|
||||||
|
celerybeat-schedule
|
||||||
|
celerybeat.pid
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
|
|
||||||
|
# pytype static type analyzer
|
||||||
|
.pytype/
|
||||||
|
|
||||||
|
# Cython debug symbols
|
||||||
|
cython_debug/
|
||||||
|
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# R.A.L.F. is a discord-bot
|
||||||
|
|
||||||
|
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):
|
||||||
|
|
||||||
|
- 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
|
||||||
|
|
||||||
|
You need the discord.py wrapper from Rapptz :
|
||||||
|
|
||||||
|
git clone https://github.com/Rapptz/discord.py
|
||||||
|
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
|
||||||
|
|
||||||
|
Now you can cd into the bot's directory and launch it with
|
||||||
|
|
||||||
|
python3 main.py
|
||||||
@@ -0,0 +1,301 @@
|
|||||||
|
import discord
|
||||||
|
from discord import app_commands
|
||||||
|
from dis_events import DiscordEvents
|
||||||
|
import random
|
||||||
|
from glob import glob
|
||||||
|
import numpy as np
|
||||||
|
import config
|
||||||
|
from secret import BOT_TOKEN, CLIENT_ID, GUILD_ID
|
||||||
|
import utils
|
||||||
|
from discord.ext import tasks
|
||||||
|
import datetime
|
||||||
|
from dateutil import tz
|
||||||
|
|
||||||
|
# #######################################
|
||||||
|
# The OMFClient class
|
||||||
|
# #######################################
|
||||||
|
|
||||||
|
|
||||||
|
class OMFClient(discord.Client):
|
||||||
|
|
||||||
|
channel_idle_timer: int
|
||||||
|
asked_question = False
|
||||||
|
last_question: discord.Message = None
|
||||||
|
guild_Events = None
|
||||||
|
lastNotifyTimeStamp = None
|
||||||
|
|
||||||
|
# #######################################
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
self.channel_idle_timer = 0
|
||||||
|
self.idle_channel = self.get_channel(config.IDLE_MESSAGE_CHANNEL_ID)
|
||||||
|
|
||||||
|
# #########################
|
||||||
|
# setup_hook waits for the
|
||||||
|
# command tree to sync
|
||||||
|
# #########################
|
||||||
|
|
||||||
|
async def setup_hook(self) -> None:
|
||||||
|
# Sync the application command with Discord.
|
||||||
|
await self.tree.sync()
|
||||||
|
|
||||||
|
# ######################################################
|
||||||
|
# send_random_message is called when the server is idle
|
||||||
|
# and posts a random message to the server
|
||||||
|
# ######################################################
|
||||||
|
|
||||||
|
async def send_random_message(self):
|
||||||
|
print("Sending random message")
|
||||||
|
if self.idle_channel == None:
|
||||||
|
self.idle_channel = self.get_channel(config.IDLE_MESSAGE_CHANNEL_ID)
|
||||||
|
await self.idle_channel.send(f"{random.choice(self.idle_messages)}")
|
||||||
|
|
||||||
|
# ######################################################
|
||||||
|
# on_ready is called once the client is initialized
|
||||||
|
# it then reads in the files in the config.IDLE_MESSAGE_DIR
|
||||||
|
# directory and posts them randomly every
|
||||||
|
# config.CHANNEL_IDLE_INTERVAL seconds into the
|
||||||
|
# config.IDLE_MESSAGE_CHANNEL_ID channel
|
||||||
|
# ######################################################
|
||||||
|
|
||||||
|
async def on_ready(self):
|
||||||
|
print('Logged on as', self.user)
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
self.idle_messages = []
|
||||||
|
|
||||||
|
for filename in glob(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)
|
||||||
|
|
||||||
|
# start the schedulers
|
||||||
|
|
||||||
|
self.task_scheduler.start()
|
||||||
|
self.daily_tasks.start()
|
||||||
|
|
||||||
|
|
||||||
|
# ######################################################
|
||||||
|
# handle_ping is called when a user sends ping
|
||||||
|
# it just replies with pong
|
||||||
|
# ######################################################
|
||||||
|
|
||||||
|
async def handle_ping (self,message : discord.Message):
|
||||||
|
await message.channel.send('pong', reference=message)
|
||||||
|
|
||||||
|
# ######################################################
|
||||||
|
# create_events will create the Sunday Funday events
|
||||||
|
# for the next sunday
|
||||||
|
# ######################################################
|
||||||
|
|
||||||
|
async def create_events (self):
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
# calculate the date of the future event
|
||||||
|
theDate:datetime.datetime = utils.onDay(datetime.date.today(),theEvent['day_of_week'])
|
||||||
|
# we need the offset from local time to UTC for the date of the event,
|
||||||
|
# not the date when we create the event. The event is on a Sunday and might be the
|
||||||
|
# first daylight saving day
|
||||||
|
utcOffset=tz.tzlocal().utcoffset(datetime.datetime.strptime(f"{theDate}","%Y-%m-%d"))
|
||||||
|
# the times are stored in local time in the dictionary but will
|
||||||
|
# be UTC in the discord API
|
||||||
|
utcStartTime=format(datetime.datetime.strptime(theEvent['start_time'],"%H:%M:%S")-utcOffset,"%H:%M:%S")
|
||||||
|
utcEndTime=format(datetime.datetime.strptime(theEvent['end_time'],"%H:%M:%S")-utcOffset,"%H:%M:%S")
|
||||||
|
# Now we just add the date portion and the time portion
|
||||||
|
# of course this will not work for events where the UTCOffset is bigger
|
||||||
|
# than the start time - for Germany this is OK as long as the start time is
|
||||||
|
# after 2 AM
|
||||||
|
strStart=theDate.strftime(f"%Y-%m-%dT{utcStartTime}")
|
||||||
|
strEnd=theDate.strftime(f"%Y-%m-%dT{utcEndTime}")
|
||||||
|
await newEvent.create_guild_event(
|
||||||
|
GUILD_ID,
|
||||||
|
theEvent['title'],
|
||||||
|
theEvent['description'],
|
||||||
|
f"{strStart}",
|
||||||
|
f"{strEnd}",
|
||||||
|
{},2,theEvent['channel'])
|
||||||
|
|
||||||
|
# once we have created the event, we let everyone know
|
||||||
|
channel = self.get_channel(config.IDLE_MESSAGE_CHANNEL_ID)
|
||||||
|
await channel.send(f'Hi - I have created the scheduled Event {theEvent["title"]}')
|
||||||
|
|
||||||
|
|
||||||
|
# ######################################################
|
||||||
|
# get_event_list gives a list of scheduled events
|
||||||
|
# ######################################################
|
||||||
|
|
||||||
|
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 newEvent.list_guild_events(GUILD_ID)
|
||||||
|
self.guild_Events = eventList
|
||||||
|
return eventList
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ######################################################
|
||||||
|
# on_message scans for message contents and takes
|
||||||
|
# corresponding actions.
|
||||||
|
# User sends ping - bot replies with pong
|
||||||
|
# User asks a question - bot checks if question has been
|
||||||
|
# answered
|
||||||
|
# ######################################################
|
||||||
|
|
||||||
|
async def on_message(self, message : discord.Message ):
|
||||||
|
print("{} has just sent {}".format(message.author, message.content))
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# reply to ping
|
||||||
|
if message.content == 'ping':
|
||||||
|
await self.handle_ping(message)
|
||||||
|
|
||||||
|
# check if there is a question
|
||||||
|
|
||||||
|
if "?" in message.content:
|
||||||
|
self.asked_question = True
|
||||||
|
self.last_question = message
|
||||||
|
else:
|
||||||
|
self.asked_question = False
|
||||||
|
self.last_question = None
|
||||||
|
|
||||||
|
|
||||||
|
# ######################################################
|
||||||
|
# on_typing detects if a user types.
|
||||||
|
# We might use this one day to have users agree to policies etc.
|
||||||
|
# before they are allowed to speak
|
||||||
|
# ######################################################
|
||||||
|
|
||||||
|
|
||||||
|
async def on_typing(self, channel, user, _):
|
||||||
|
# we do not want the bot to reply to itself
|
||||||
|
if user.id == self.user.id:
|
||||||
|
return
|
||||||
|
print(f"{user} is typing in {channel}")
|
||||||
|
self.channel_idle_timer = 0
|
||||||
|
#await channel.trigger_typing()
|
||||||
|
|
||||||
|
# ######################################################
|
||||||
|
# daily_tasks runs once a day and checks the following:
|
||||||
|
# - does an event need to be created ?
|
||||||
|
# ######################################################
|
||||||
|
|
||||||
|
@tasks.loop(hours=24)
|
||||||
|
async def daily_tasks(self):
|
||||||
|
print("DAILY TASKS")
|
||||||
|
|
||||||
|
# Every Monday we want to create the scheduled events
|
||||||
|
# for the next Sunday
|
||||||
|
|
||||||
|
if datetime.date.today().weekday() == 0:
|
||||||
|
print("create events")
|
||||||
|
try:
|
||||||
|
await self.create_events()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Daily Task create Events failed: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
# ######################################################
|
||||||
|
# task_scheduler is the main supervisor task
|
||||||
|
# it runs every 10 minutes and checks the following:
|
||||||
|
# - has a question been asked that has not been answered ?
|
||||||
|
# - do reminders need to be sent out ?
|
||||||
|
# - does a random message need to be sent out ?
|
||||||
|
# ######################################################
|
||||||
|
|
||||||
|
|
||||||
|
@tasks.loop(minutes=10)
|
||||||
|
async def task_scheduler(self):
|
||||||
|
|
||||||
|
self.channel_idle_timer += 1
|
||||||
|
print("SCHEDULE")
|
||||||
|
|
||||||
|
# #####################################
|
||||||
|
# See if there are unanswered questions
|
||||||
|
# #####################################
|
||||||
|
try:
|
||||||
|
if(self.asked_question):
|
||||||
|
print("scheduler: Question")
|
||||||
|
print(self.last_question.created_at)
|
||||||
|
# TODO - we need to send out a message to the @here role
|
||||||
|
# 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
|
||||||
|
if self.channel_idle_timer > config.QUESTION_SLEEPING_TIME:
|
||||||
|
print("QUESTION WITHOUT REPLY")
|
||||||
|
self.asked_question = False
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Scheduler question failed: {e}")
|
||||||
|
|
||||||
|
# #####################################
|
||||||
|
# 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.CHANNEL_IDLE_INTERVAL:
|
||||||
|
self.channel_idle_timer = 0
|
||||||
|
await self.send_random_message()
|
||||||
|
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:
|
||||||
|
# first let's convert the String dates to datetime:
|
||||||
|
theDate=utils.onDay(datetime.date.today(),theEvent['day_of_week'])
|
||||||
|
startTime=theEvent['start_time']
|
||||||
|
eventScheduledTimeDate=datetime.datetime.fromisoformat (theDate.strftime(f"%Y-%m-%dT{startTime}"))
|
||||||
|
# now let's figure out the time deltas:
|
||||||
|
timeUntilEvent=eventScheduledTimeDate - datetime.datetime.today()
|
||||||
|
earliestPing=eventScheduledTimeDate - datetime.timedelta(minutes=theEvent["notify_minutes"]) - datetime.timedelta(minutes=10)
|
||||||
|
latestPing=eventScheduledTimeDate - datetime.timedelta(minutes=theEvent["notify_minutes"])
|
||||||
|
#print("found scheduled event")
|
||||||
|
#print(f"The event is on {theDate} at {startTime} - that is in {timeUntilEvent}")
|
||||||
|
#print (f"we ping between {earliestPing} and {latestPing}")
|
||||||
|
# If we are in the interval then let's initiate the reminder
|
||||||
|
if (earliestPing < datetime.datetime.today() <= latestPing):
|
||||||
|
# let's first check if the event is still on
|
||||||
|
# it may have been deleted or modified on the server
|
||||||
|
# we don't want to alert for non-existing events
|
||||||
|
# we'll just use the title to compare for the time being.
|
||||||
|
print("Let me check if the event is still on")
|
||||||
|
try:
|
||||||
|
if eventList == None:
|
||||||
|
newEvent = DiscordEvents(BOT_TOKEN,f'https://discord.com/api/oauth2/authorize?client_id={CLIENT_ID}&permissions=8&scope=bot')
|
||||||
|
eventList = await newEvent.list_guild_events(GUILD_ID)
|
||||||
|
for theScheduledEvent in eventList:
|
||||||
|
if theScheduledEvent["name"] == theEvent["title"]:
|
||||||
|
channel = self.get_channel(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"]}'
|
||||||
|
await channel.send(f"{theMessageText}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Scheduler event_reminder failed: {e}")
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
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!****
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
Hi, this is R.A.L.F., your assistant bot.
|
||||||
|
|
||||||
|
Please check in every now and then to the <#866779182293057566> channel.
|
||||||
|
This is where folks here on the server ask for help.
|
||||||
|
Maybe **** YOU **** can help someone there ?
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
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?
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
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.
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
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 hep 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 ?
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
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 ?
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
# the number of scheduler cycles that the channel needs to be idle
|
||||||
|
# before the bot posts a generic "did you know"
|
||||||
|
# message
|
||||||
|
|
||||||
|
CHANNEL_IDLE_INTERVAL: int = 4
|
||||||
|
|
||||||
|
# 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_DIR: str = "bot_messages"
|
||||||
|
|
||||||
|
# the channel where the bot will post messages to
|
||||||
|
|
||||||
|
IDLE_MESSAGE_CHANNEL_ID = 758271650226765848
|
||||||
|
|
||||||
|
# Variable that indicates when the bot answers after a question has been asked
|
||||||
|
# (in scheduler cycles)
|
||||||
|
|
||||||
|
QUESTION_SLEEPING_TIME = 2
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# 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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
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()
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import bot
|
||||||
|
import secret
|
||||||
|
|
||||||
|
client = bot.OMFClient()
|
||||||
|
client.run(secret.BOT_TOKEN)
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# You need to create a file called secret.py that contains your
|
||||||
|
# discord token. This is just an example. Replace the tokens with your own !
|
||||||
|
|
||||||
|
# the BOT_TOKEN is the Oauth2 token for your bot
|
||||||
|
|
||||||
|
BOT_TOKEN: str = "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
|
||||||
|
|
||||||
|
GUILD_ID: str = "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
|
||||||
|
|
||||||
|
CLIENT_ID: str = "9990236500564536"
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import datetime
|
||||||
|
|
||||||
|
# ################################################
|
||||||
|
# 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).
|
||||||
|
#
|
||||||
|
# Monday=0, Tuesday=1 .... Sunday=6
|
||||||
|
# ################################################
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
return date + datetime.timedelta(days=days)
|
||||||
|
|
||||||
Reference in New Issue
Block a user