diff --git a/.pylintrc b/.pylintrc
index 95985b07..44f856b4 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -572,5 +572,5 @@ min-public-methods=2
# Exceptions that will emit a warning when being caught. Defaults to
# "BaseException, Exception".
-overgeneral-exceptions=BaseException,
- Exception
+overgeneral-exceptions=builtins.BaseException,
+ builtins.Exception
diff --git a/Installation.md b/Installation.md
index 2b874ecb..b8bbdc47 100644
--- a/Installation.md
+++ b/Installation.md
@@ -23,6 +23,7 @@ To create a Discord Bot, you must:
TESTING_BOT_TOKEN={test-bot-token}
TEST_BOT_NAME={test-bot-name}
TEST_BOT_APP_ID={test-bot-application-id}
+ BARD_API_KEY={your-bard-api-key}
VERSION={custom bot version}
```
@@ -60,10 +61,11 @@ To run tests on the Teacher's Pet Bot:
TESTING_BOT_TOKEN={test-bot-token}
TEST_BOT_NAME={test-bot-name}
TEST_BOT_APP_ID={test-bot-application-id}
+ BARD_API_KEY={your-bard-api-key}
VERSION={custom bot version}
```
3. In `test/tests.py`, update the `TEST_GUILD_ID` to be the id of the server/guild you are testing in.
- 4. Start Teacher's Pet Bot by running one of the following commands in the root directory of the project:
+ 4. IMPORTANT(You will have two bots running at once for testing. One normal bot, one test bot): Start Teacher's Pet Bot by running one of the following commands in the root directory of the project:
* Without Coverage: `pytest src/bot.py`
* With Coverage: `coverage run --source=./src -m pytest src/bot.py`
5. Run the tests with `python test/tests.py` in the root directory of the project
diff --git a/README.md b/README.md
index 681648eb..340ecf30 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,3 @@
-https://drive.google.com/file/d/1AN1_ogV7DrMxMMEdWf1HOMlxwH-fbyPm/view?usp=sharing
-
Teacher's Pet
@@ -31,7 +29,7 @@ https://drive.google.com/file/d/1AN1_ogV7DrMxMMEdWf1HOMlxwH-fbyPm/view?usp=shari
-Click Below to Watch Our Video!
+Click Below to Watch The Video!
@@ -39,12 +37,12 @@ https://user-images.githubusercontent.com/60410421/143972105-f5aabb10-73e3-454a-
-Software Engineering Project for CSC 510 : Phase III
+Software Engineering Project for CSC 510 : Phase IV
Teacher's Pet is a Discord Bot for class instructors to streamline their Discord servers. Discord is a great tool for communication and its functionalities can be enhanced by bots and integrations.
-For 3.0, we created new tools for instructors and students to use to improve course communication. Some of our implemented features were partly suggested by iteration 2 such as regrade requests, project event, and live spam checking. We implemented other features we thought would be helpful such as email interactions, link saving, and data visualization. Our main objective for 3.0 was to add more organizational tools to make a course's discord channel more than just a messenger.
+For 4.0, we created new tools for instructors and students to use to improve course communication. After version 3.0's success, we decided we wanted to improve upon some of its features including spam and saved links. We also wanted to add a few more exciting features that would really drive engagement with our bot, for example, the AI chat and student ranking feature. Our main objective for 4.0 was to make using Discord a more controlled and enjoyable experience.
@@ -52,55 +50,18 @@ For 3.0, we created new tools for instructors and students to use to improve cou
Bot Features
-[Click here to see the features of iterations I and II.](https://github.com/chandur626/TeachersPetBot/blob/update-readme/docs/feature-history.md)
-
-
-Charts
-Instructors (like TAs, and Professors) can quickly make graphcs and charts directly in discord to share with students/users. Instructors can use this feature to share grade distributions, lecture participation/attendance, or other course statistics. All charts are named and stored into a json file when they are created. Students have acess to a command that allows them to view previously presented charts.
-
-
-
-
-Email Configuration
+[Click here to see the features of iterations I, II and III.](docs/feature-history.md)
-This feature enables users to configure their email address in the system to receive important notifications, attachments from professors, assignment reminders. Users can also update, view and unconfigure a configured email address through the system.
+Bard AI
-
+Upgraded Spam Detection
+This feature is used to detect spam in message channels. First instructors have the ability to fully customize their spam detection settings.
-
-Email Interaction
-
-This feature notifies all students regarding the next assignment deadline which is due for a day through email.
-
-
+
-
-Re-Grading
-
-This feature provides a way for students to submit regrade requests and Instructors can collect information of the requests submitted. There are various commands included to add, update, display and remove regrade requests.
-This usecase was based on regrade request submission for CSE 510 SE FALL21 mid examination.
+When a user tries to send too many messages to the channel, it gives a warning. When a warned user continues to spam after this, they will be temporarily put in timeout, effectively ending their spam. This is a good feature to help keep rogue students in check, while also stopping students from spamming for rank.
-
-
-
-Link Saving
-
-This feature is helpful to save all the messages which contain important URLs. we have built a user command "!send_links" This command lets users access all messages which contain URLs. The messages Containing URLs are automatically get appended in a file and the file is attached when the "!send_links" command is input.
-
-
-
-
-Project Event
-
-This feature allows instructors or teaching assistants to create a project event by providing information such as description, link for project submission and deadline. The deadline reminder is taken care of Email Interaction feature.
-
-
-
-
-Spam Detection
-This feature is used to detect spam in message channels. When a user tries to send too many messages in the channel, it gives a warning. This is useful when multiple users are trying to send mutiple messages. The warning lets the student know that they have sent too many messages.
-
-
+
@@ -113,47 +74,27 @@ In addition to the packages from [requirements.txt](https://github.com/tanmaypar
* [Python 3.9.7](https://www.python.org/downloads/)
* [Sqlite](https://www.sqlite.org/download.html)
-To install and run Teacher's Pet, follow instructions in the [Installation and Testing Guide](https://github.com/Ashwinshankar98/TeachersPetBot/blob/main/Installation.md).
+To install and run Teacher's Pet, follow the instructions in the [Installation and Testing Guide](Installation.md).
Testing
-To run tests on the Teacher's Pet, follow instructions in the [Installation and Testing Guide](https://github.com/Ashwinshankar98/TeachersPetBot/blob/main/Installation.md#Run-Tests).
+To run tests on the Teacher's Pet, follow instructions in the [Installation and Testing Guide](Installation.md).
Bot Commands
- Bot commands from iteration III
-
+ Bot commands from iteration IV
-:open_file_folder: [!regrade-request command](https://github.com/chandur626/TeachersPetBot/blob/main/docs/Regrade/Regrade.md)
-:open_file_folder: [!update-request command](https://github.com/chandur626/TeachersPetBot/blob/main/docs/Regrade/Regrade.md)
-
-:open_file_folder: [!remove-request command](https://github.com/chandur626/TeachersPetBot/blob/main/docs/Regrade/Regrade.md)
-
-:open_file_folder: [!display-requests command](https://github.com/chandur626/TeachersPetBot/blob/main/docs/Regrade/Regrade.md)
-
-:open_file_folder: [!chart command](https://github.com/chandur626/TeachersPetBot/blob/update-readme/docs/charts/charts.md)
-
-:open_file_folder: [!check_chart command](https://github.com/chandur626/TeachersPetBot/blob/update-readme/docs/charts/check_chart.md)
-
-:open_file_folder: [!create_email_command](https://github.com/chandur626/TeachersPetBot/blob/update-readme/docs/email_address/create_email.md)
-
-:open_file_folder: [!view_email_command](https://github.com/chandur626/TeachersPetBot/blob/update-readme/docs/email_address/view_email.md)
-
-:open_file_folder: [!update_email_command](https://github.com/chandur626/TeachersPetBot/blob/update-readme/docs/email_address/update_email.md)
-
-:open_file_folder: [!remove_email_command](https://github.com/chandur626/TeachersPetBot/blob/update-readme/docs/email_address/remove_email.md)
-
-:open_file_folder: [!project_event command](https://github.com/chandur626/TeachersPetBot/blob/update-readme/docs/events/project_event.md)
+:open_file_folder: [!set_spam_settings command](docs/spam/set_spam_settings.md)
- Bot commands from iteration I and II
+ Bot commands from iteration I, II and III
`!setInstructor <@member>` Set a server member to be an instructor (Instructor command)
@@ -187,6 +128,30 @@ To run tests on the Teacher's Pet, follow instructions in the [Installation and
`!stats` Gets the statistics of system and softwares used
+`!regrade-request` This command lets a student add a regrade-request
+
+`!update-request` This command lets a student update an existing regrade-request
+
+`!remove-request` This command removes a regrade request
+
+`!display_requests` This command lets a student display all regrade requests
+
+`!chart` This command lets admins make a custom chart of any type with any size of dataset
+
+`!check_chart` This command lets students/users check any chart if previously created
+
+`!create_email` This command enables users to configure their email address to receive important reminder notifications and attachments
+
+`!view_email` This command enables users to view their configured email address
+
+`!update_email` This command enables users to update their configured email address
+
+`!remove_email` This command enables users to delete their configured email address
+
+`!create -> press project button` This command enables users to create a project
+
+
+
@@ -195,17 +160,14 @@ To run tests on the Teacher's Pet, follow instructions in the [Installation and
This bot has endless possibilities for functionality. Features which we are interested in adding but did not have time for include but are not limited to:
- * Custom Events
- * Allow events to be edited
- * Show error information on discord
- * Make Instructor commands private
- * Add new roles
- * Track participation and user ranking
+ * Adding detailed error display integration to the bot
+ * Add Tutor role
+ * Add ways for users to lose exp in the rank feature(spamming etc)
+ * Add commands so an instructor can customize how rank exp is awarded(what for and how much etc)
* Refactor code to use cogs
- * Save data charts on DB rather than locally in json
- * Store data based on user emote reactions to instructor messages
-
-For a detailed description of each of the above future enhancements listed visit [Future Scope](https://github.com/chandur626/TeachersPetBot/projects/2).
+ * Add a gibberish detector that deletes comments that are irrelevant to the class
+ * Funnel the AI chat responses to a limited set(so AI only answers questions an instructor wants them to answer)
+ * Upgrade to a better chatbot API that is free
diff --git a/docs/feature-history.md b/docs/feature-history.md
index d1531e6c..c4026db1 100644
--- a/docs/feature-history.md
+++ b/docs/feature-history.md
@@ -183,3 +183,55 @@ Since the bot is hosted on cloud ( In this case Heroku ). Its crucial to know th
For the purpose of Debugging and maintaining the bot `!stats` command has been added to keep track of CPU usage, Bot up time, Bot version, No. of users and Memory usage.
![alt text](https://github.com/Ashwinshankar98/TeachersPetBot/blob/main/images/stats_command.png)
+
+
+ Iteration III
+
+Charts
+Instructors (like TAs, and Professors) can quickly make graphcs and charts directly in discord to share with students/users. Instructors can use this feature to share grade distributions, lecture participation/attendance, or other course statistics. All charts are named and stored into a json file when they are created. Students have acess to a command that allows them to view previously presented charts.
+
+
+
+
+Email Configuration
+
+This feature enables users to configure their email address in the system to receive important notifications, attachments from professors, assignment reminders. Users can also update, view and unconfigure a configured email address through the system.
+
+
+
+
+Email Interaction
+
+This feature notifies all students regarding the next assignment deadline which is due for a day through email.
+
+
+
+
+Re-Grading
+
+This feature provides a way for students to submit regrade requests and Instructors can collect information of the requests submitted. There are various commands included to add, update, display and remove regrade requests.
+This usecase was based on regrade request submission for CSE 510 SE FALL21 mid examination.
+
+
+
+
+Link Saving
+
+This feature is helpful to save all the messages which contain important URLs. we have built a user command "!send_links" This command lets users access all messages which contain URLs. The messages Containing URLs are automatically get appended in a file and the file is attached when the "!send_links" command is input.
+
+
+
+
+Project Event
+
+This feature allows instructors or teaching assistants to create a project event by providing information such as description, link for project submission and deadline. The deadline reminder is taken care of Email Interaction feature.
+
+
+
+
+Spam Detection
+This feature is used to detect spam in message channels. When a user tries to send too many messages in the channel, it gives a warning. This is useful when multiple users are trying to send mutiple messages. The warning lets the student know that they have sent too many messages.
+
+
+
+
diff --git a/docs/media/set_spam.gif b/docs/media/set_spam.gif
new file mode 100644
index 00000000..2d7c1b35
Binary files /dev/null and b/docs/media/set_spam.gif differ
diff --git a/docs/media/spam.gif b/docs/media/spam.gif
new file mode 100644
index 00000000..3ee10a0e
Binary files /dev/null and b/docs/media/spam.gif differ
diff --git a/docs/spam/set_spam_settings.md b/docs/spam/set_spam_settings.md
new file mode 100644
index 00000000..6e36ba17
--- /dev/null
+++ b/docs/spam/set_spam_settings.md
@@ -0,0 +1,40 @@
+# About !set_spam_settings
+This command lets instructors set their own customized spam settings
+
+# Location of Code
+The code that implements the above mentioned gits functionality is located [here](../../src/spam.py).
+
+# Code Description
+## Functions
+
+- async def set(ctx):
+This function prompts the instructor (only inside the instructor commands channel) for what settings they would like to apply the spam detector. For example, instructors are allowed to set how many messages per unit of time a student is allowed to send before they are warned of spamming, and when they are timed out. Also they are allowed to set how long the timeout will last. Then the function takes all the input prompts and places them into the database so the spam detector knows how to do its job correctly.
+
+# How to run it? (Small Example)
+Enter: "!set_spam_settings"
+
+This will be followed by the prompt: How many messages should a user be able to send before they are put in timeout?
+
+Enter how many messages a user should be able to send before warned of spam: "3"
+
+This will be followed by the prompt: How many messages should a user be able to send before they are put in timeout?
+
+Enter how many meesages a user should be able to send before they are put in timeout: "4"
+
+This will be followed by the prompt: How much time should users have to send 3 messages before they will be warned and then eventually put in timeout?
+
+Enter the unit of time (in seconds) over which students are allowed to send 3 and 4 messages without being warned of spam and timed out respectively (In other words what you enter in how much are users allowed to send x messages. If the user sends x+1 messages in this amount of time, then they will be accused of spamming): "10"
+
+This will be followed by the prompt: How long should a user be placed in timeout when caught spamming? Format: min,hours,days Example: 5,0,0 is 5 minutes
+
+Enter the amount of time a user should be placed in timeout in the format outlined above: 10,0,0
+
+Below is an example of using the !set_spam_settings command:
+
+
+Successful execution will result in a spam detector similar to below.
+
+
+
+# Use Case Example
+Let's say you are an English professor and you have a class discord that you use for your live classes. Let's say you often ask multiple questions at once to your students. For example your class just read a book and you send out 10 questions for them to respond to on the chat. Some students may answer all 10 questions very quickly, so quickly in fact they could be banned by the default spam settings since if they send more than 5 messages in 15 seconds they will be banned for spamming. Luckily, our teacher's pet bot offers the !set_spam_settings command such that you can change the number of messages to 15 for days where you will be asking multiple questions at once. Then when students answer your question very quickly, they won't be accused of spamming. But even still, if a mean student set a spam bot loose on the discord it would be banned for spam if it broke your new set threshold. So with our highly customizable spam command, you can set your spam settings to meet whatever needs you require.
diff --git a/docs/spam/spam.md b/docs/spam/spam.md
index 9b68cce1..820e24d5 100644
--- a/docs/spam/spam.md
+++ b/docs/spam/spam.md
@@ -1,29 +1,55 @@
# Location of Codehttps:
-The code that implements the above commands is located [here](https://github.com/chandur626/TeachersPetBot/blob/main/src/bot.py)
+The code that implements the above commands is located [here](../../src/spam.py)
# Code Description
+This code implements a spam detector that issues warnings to students who send too many messages based on a message threshold and a time-allowed threshold. If a user sends more messages than allowed in a certain amount of time, then that user will be put in time-out after a warning.
+
## Functions
-on_message(message):
+def init(bot):
+
+This function takes a bot as an argument and uses that bot to set the bot for the spam.py file. Additionally, this function creates a task to be constantly running called clear_spam(), which essentially determines the amount of time a user is allowed to send x amount of messages before being detected as spam. Init also inserts default spam setting values into the database if the bot is new to the guild.
+
+ warning_num = 4 # number of messages before warning of spam
+ timeout_num = 5 # number of messages before timeout
+ timeout_min = 5 # number of minutes in timeout
+ timeout_hour = 0 # hours in timeout
+ timeout_day = 0 # days in timeout
+ time_between_clears = 15 # time threshold mentioned above
+
+These values are then inserted into the database if values weren't already there.
+
+async def clear_spam():
-This function takes as arguments the message in context and performs spam detection. It keeps track of the number of messages a user sent in a file called [spam.txt](https://github.com/chandur626/TeachersPetBot/blob/main/src/spam.txt). This file keeps track of the number of message a user sent in the channel. Once the limit is hit, the file auto clears.
+This function constantly runs every x amount of seconds, where x is time_between_clears aka the time threshold in the bot's database. This function deletes everything in the spam.txt file such that every x seconds the spam detector can start from 0 when counting someone's messages for spam. For example, if a user sends 30 messages in 5 seconds that is spam, but if a user sends 30 messages in 1 hour that should not be spam. So the clear_spam function clears the spam.txt file every x seconds to make sure users are fairly evaluated on whether they are spamming or not.
+
+async def handle_spam(message, ctx, guild_id)
+
+This function is called from bot.py on every message that is sent by non-instructors in the guild. This function opens up spam.txt to count how many times the particular author of the last message sent has sent messages in the guild in the past x amount of seconds, where x is the value set in the database 'time_between_clears'. Then the function looks to see if that count exceeds the warning threshold setting and the timeout threshold setting which are also in the database. If the user sends more messages than the warning threshold then this function sends a message warning that the user is spamming too much. If the user sends more messages than the timeout threshold, then the user is put into timeout. The function then queries the database to decide how long to put the user in timeout, based on what the instructor set in the database, or what was there by default.
# How to run it? (Small Example)
This code runs everytime a message is sent in the channel.
## An example
-### Send the following messages in the channel:
-#### message1
-#### message2
-#### message3
-#### message4
-#### message5
-#### message6
-The channel will now give a warning saying too many messages.
+### Send the following messages in the channel(must do this faster than in 15 seconds):
+Message: h
+Message: h
+Message: h
+Message: h
+Message: h
+By the default settings, the channel will now give a warning saying too many messages.
+
+### Send on more message immediately following:
+#### halkneoihan
+By the default settings, as long as you are not the instructor or an admin, you will be placed in timeout for 5 minutes.
+
+# Use Case Example
+The main use case of this feature is that if you are an instructor running an online class you wouldn't want students to just spam your chat with constant gibberish. For example, let's say you are the chem professor with the chem discord and you are running office hours over discord. Suddenly one of the students in the office hour accidently spills something on their keyboard causing it to spam the text 'a' over and over again. Suddenly it would be impossible to read questions from your other students. However, if you are using our teacher's pet bot, it will automatically handle the situation by temporarily putting the student in timeout such that they won't be spamming the chat anymore.
-When a user tries to send more than 5 (current threshold) messages in a channel, it gives a warning message saying that the user is trying to send too many messages. This functinality is specific to users. It doesn't stop the user from sending messages even it they hit their limit.
+Below is what the feature looks like in action:
+
diff --git a/src/bot.py b/src/bot.py
index 18980077..cc59d18e 100644
--- a/src/bot.py
+++ b/src/bot.py
@@ -32,6 +32,7 @@
import help_command
import regrade
import utils
+import spam
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '1'
@@ -47,6 +48,7 @@
BOT_VERSION=os.getenv('VERSION')
print(BOT_VERSION)
Test_bot_application_ID = int(os.getenv('TEST_BOT_APP_ID'))
+guild_id = int(os.getenv('TEST_GUILD_ID')) ## needed for spam detection
TESTING_MODE = None
@@ -63,7 +65,6 @@ async def on_ready():
''' run on bot start-up '''
global TESTING_MODE
TESTING_MODE = False
-
#DiscordComponents(bot)
db.connect()
db.mutation_query('''
@@ -121,15 +122,25 @@ async def on_ready():
is_active BOOLEAN NOT NULL CHECK (is_active IN (0, 1))
)
''')
-
+ db.mutation_query('''
+ CREATE TABLE IF NOT EXISTS spam_settings (
+ warning_num INT,
+ timeout_num INT,
+ timeout_min INT,
+ timeout_hour INT,
+ timeout_day INT,
+ time_between_clears INT
+ )
+ ''')
event_creation.init(bot)
office_hours.init(bot)
- await cal.init(bot)
+ spam.init(bot) #initialize the spam function of the bot so spam.py has
+ # access to the bot and clearing starts
print('Logged in as')
print(bot.user.name)
print(bot.user.id)
print('------')
-
+ await cal.init(bot) ##this needed to be moved below bc otherwise the stuff above is never called
###########################
# Function: on_guild_join
# Description: run when a the bot joins a guild
@@ -238,30 +249,23 @@ async def on_member_remove(member):
@bot.event
async def on_message(message):
''' run on message sent to a channel '''
- #spam detection
-
url_data=[]
message_links = []
temp=[]
ctx = await bot.get_context(message)
- print(message.content)
- print(message.author.id)
- count = 0
- with open("spam.txt", "a",encoding='utf-8') as f:
- f.writelines(f"{str(message.author.id)}\n")
-
- with open("spam.txt","r+",encoding='utf-8') as f:
- for line in f:
- if line.strip("\n") == str(message.author.id):
- count = count+1
-
- if count>5:
- f.truncate(0)
+ member = message.guild.get_member(message.author.id)
+ instructor = False
+ for role in member.roles:
+ if role.name == 'Instructor':
+ instructor = True
+ if not instructor:
+ # Only spam detect on non instructors
+ await spam.handle_spam(message, ctx, guild_id) # handles spam
# allow messages from test bot
- print(message.author.bot)
- print(message.author.id)
- print(Test_bot_application_ID)
+ #print(message.author.bot)
+ #print(message.author.id)
+ #print(Test_bot_application_ID)
if message.author.bot and message.author.id == Test_bot_application_ID:
ctx = await bot.get_context(message)
await bot.invoke(ctx)
@@ -409,6 +413,21 @@ async def create_event(ctx):
''' run event creation interface '''
await event_creation.create_event(ctx, TESTING_MODE)
+###########################
+# Function: create_event
+# Description: command to create event and send to event_creation module
+# Ensures command author is Instructor
+# Inputs:
+# - ctx: context of the command
+# Outputs:
+# - Options to create event
+###########################
+@bot.command(name='set_spam_settings', help='Allows instructor to set spam settings')
+@commands.has_role('Instructor')
+async def set_spam_settings(ctx):
+ ''' run spam setting prompts '''
+ await spam.set(ctx)
+
###########################
# Function: oh
# Description: command related office hour and send to office_hours module
@@ -705,7 +724,7 @@ async def show_stats(ctx):
''' run on message edited '''
embed = Embed(title="Bot stats",
colour=ctx.author.colour,
- thumbnail=bot.user.avatar_url,
+ #thumbnail=bot.user.avatar_url,
timestamp=datetime.utcnow())
proc = Process()
@@ -956,7 +975,6 @@ async def begin_tests(ctx):
if test_oh_chan:
await office_hours.close_oh(ctx.guild, 'test')
await office_hours.open_oh(ctx.guild, 'test')
-
###########################
# Function: end_tests
# Description: Finalize automated testing
diff --git a/src/cal.py b/src/cal.py
index f3275b98..31b85ec9 100644
--- a/src/cal.py
+++ b/src/cal.py
@@ -115,7 +115,6 @@ def update_calendar(ctx):
async def init(b):
''' initialize the calendar '''
global BOT
-
BOT = b
for guild in BOT.guilds:
for channel in guild.text_channels:
@@ -126,7 +125,7 @@ async def init(b):
await display_events(channel)
#close calls on assignments and exams
await closecalls.start(channel)
-
+ print('I dont get here')
@@ -211,9 +210,9 @@ async def closecalls(ctx): # pragma: no cover
if not MSG_CC_NONE:
MSG_CC_NONE = await ctx.send(embed=CLOSE_CALL_EMBED_NONE)
else:
+
# otherwise, edit the saved message from earlier
await MSG_CC_NONE.edit(embed=CLOSE_CALL_EMBED_NONE)
-
@closecalls.before_loop
async def before(): # pragma: no cover
await BOT.wait_until_ready()
diff --git a/src/event_creation.py b/src/event_creation.py
index 2e770555..6cb05387 100644
--- a/src/event_creation.py
+++ b/src/event_creation.py
@@ -4,12 +4,15 @@
import datetime
from datetime import timedelta
from discord.ui import Button, Select, View
-from discord import SelectOption, ButtonStyle, Interaction
-import validators
+from discord import SelectOption, ButtonStyle
from discord.utils import get
-from discord.ext import tasks, commands
+from discord.ext import tasks
+
+import validators
+
from utils import EmailUtility
+
import office_hours
import cal
import db
@@ -50,11 +53,13 @@ def check(m):
button_clicked = (await BOT.wait_for('button_click')).custom_id
"""
async def button_callback(interaction):
- await interaction.response.send_message('What would you like the assignment to be called')
+ await interaction.response.send_message('What would you like the assignment to '
+ 'be called')
msg = await BOT.wait_for('message', timeout = 60.0, check = check)
title = msg.content.strip()
- await ctx.send('What is the due date of this assignment?\nEnter in format `MM-DD-YYYY`')
+ await ctx.send('What is the due date of this assignment?\nEnter in format '
+ '`MM-DD-YYYY`')
msg = await BOT.wait_for('message', timeout = 60.0, check = check)
date = msg.content.strip()
try:
@@ -130,7 +135,8 @@ async def project_button_callback(interaction):
)
await ctx.send('Project successfully created!')
await cal.display_events(ctx) ## needed so the calander updates
- button3.callback = project_button_callback #assigns the project button to the function directly above
+ button3.callback = project_button_callback #assigns the project button to the function
+ # directly above
async def exam_button_callback(interaction):
await interaction.response.send_message('What is the title of this exam?')
@@ -240,7 +246,8 @@ async def office_hour_button_callback(interaction):
day_view.add_item(day_select)
#function to respond to instructor selection
async def instructor_select_callback(interaction):
- instructor = instructor_select.values[0] ## assigns instructor with the selected instructor
+ instructor = instructor_select.values[0] ## assigns instructor with the selected
+ # instructor
print(instructor)
await interaction.response.send_message(
'Which day would you like the office hour to be on?',
@@ -257,7 +264,8 @@ async def instructor_select_callback(interaction):
async def day_select_callback(interaction):
day = day_select.values[0]
print(day)
- await interaction.response.send_message('What is the start time of the office hour?\nEnter in 24-hour format' +
+ await interaction.response.send_message('What is the start time of the office '
+ 'hour?\nEnter in 24-hour format' +
' e.g. an starting at 1:59pm can be inputted as 13:59')
msg = await BOT.wait_for('message', timeout = 60.0, check = check)
t_start = msg.content.strip()
@@ -291,8 +299,10 @@ async def day_select_callback(interaction):
)
await ctx.send('Office hour successfully created!')
- await cal.display_events(ctx) ## updates the calender channel on discord with new office hours
- day_select.callback = day_select_callback ## sets the day selection button to call to the correct function
+ await cal.display_events(ctx) ## updates the calender channel on discord with
+ # new office hours
+ day_select.callback = day_select_callback ## sets the day selection button to call to
+ # the correct function
button4.callback = office_hour_button_callback
else:
await ctx.author.send('`!create` can only be used in the `instructor-commands` channel')
diff --git a/src/qna.py b/src/qna.py
index f72dda42..bfa423d7 100644
--- a/src/qna.py
+++ b/src/qna.py
@@ -74,7 +74,7 @@ async def question(ctx, qs):
###########################
async def answer(ctx, num, ans):
''' answer the specific question '''
- if int(num) not in QNA.keys():
+ if int(num) not in QNA:
await ctx.author.send('Invalid question number: ' + str(num))
# delete user msg
await ctx.message.delete()
diff --git a/src/spam.py b/src/spam.py
new file mode 100644
index 00000000..2733a9db
--- /dev/null
+++ b/src/spam.py
@@ -0,0 +1,161 @@
+import asyncio
+from datetime import timedelta
+import discord
+import db
+
+
+
+BOT = None
+###########################
+# Function: set
+# Description: prompts the instructor to set the spam settings
+# Inputs:
+# - ctx: the context of the channel we are on
+###########################
+async def set(ctx):
+ if ctx.channel.name == 'instructor-commands':
+ # only run in the instructor command channel
+ await ctx.send('How many messages should a user be able to send before they are warned '
+ 'of spamming?')
+ try:
+ print("lord...")
+ msg = (await BOT.wait_for('message', timeout=60.0, check=lambda x: x.channel.name ==
+ 'instructor-commands' and x.author.id != BOT.user.id)).content
+ print(f'this is the fucked msg {msg}')
+ msg = int(msg)
+ print(msg)
+ msg_warn_num = msg
+ except:
+ await ctx.send('Invalid entry. Try again...')
+ return
+
+ await ctx.send('How many messages should a user be able to send before they are put in '
+ 'timeout?')
+ try:
+ msg_timeout_num = int((await BOT.wait_for('message', timeout=60.0, check=lambda x:
+ x.channel.name ==
+ 'instructor-commands' and x.author.id != BOT.user.id)).content)
+ except:
+ await ctx.send('Invalid entry. Try again...')
+ return
+
+ await ctx.send(
+ f'How much time should users have to send {msg_warn_num} messages before they will be '
+ f'warned '
+ f'and then eventually put in timeout?')
+ try:
+ msg_time_clears = int((await BOT.wait_for('message', timeout=60.0, check=lambda x:
+ x.channel.name ==
+ 'instructor-commands' and x.author.id != BOT.user.id)).content)
+ except:
+ await ctx.send('Invalid entry. Try again...')
+ return
+
+ await ctx.send('How long should a user be placed in timeout when caught spamming? '
+ 'Format: min,hours,days Example: 5,0,0 is 5 minutes')
+ try:
+ msg_timeout_time = (await BOT.wait_for('message', timeout=60.0, check=lambda x:
+ x.channel.name ==
+ 'instructor-commands' and x.author.id != BOT.user.id)).content
+ timeout_time = msg_timeout_time.split(',')
+ minutes = int(timeout_time[0])
+ hours = int(timeout_time[1])
+ days = int(timeout_time[2])
+ timedelta(seconds=0, minutes=minutes, hours=hours, days=days)
+ except:
+ await ctx.send('Invalid entry. Try again...')
+ return
+ db.mutation_query(
+ 'UPDATE spam_settings SET warning_num = ?, timeout_num = ?, timeout_min = ?, '
+ 'timeout_hour = ?, timeout_day = ?, time_between_clears = ?',
+ [msg_warn_num, msg_timeout_num, int(timeout_time[0]), int(timeout_time[1]),
+ int(timeout_time[2]), msg_time_clears]
+ )
+ await ctx.send('Spam settings successfully updated!')
+ else:
+ await ctx.author.send('`!set_spam_settings` can only be used in the `instructor-commands` '
+ 'channel')
+ await ctx.message.delete()
+###########################
+# Function: clear_spam
+# Description: run as a constant task that clears the spam.txt file
+# Inputs:
+# - None
+###########################
+async def clear_spam():
+ while True:
+ rows = db.select_query('SELECT * FROM spam_settings')
+ rows_tuple = rows.fetchall()[0]
+ time_between_clears = rows_tuple[5]
+ # print("We cleared") ## for testing purposes
+ await asyncio.sleep(time_between_clears) # uses a set seconds betweeen spam clearing
+ with open("spam.txt", "r+", encoding='utf-8') as f:
+ f.truncate(0) # delete the user_id of the last message sent
+###########################
+# Function: init
+# Description: initializes this module, giving it access to discord bot. Also inits the clear
+# spam function and
+# puts default values in for the spam settings.
+# Inputs:
+# - bot: discord bot
+# Outputs: None
+###########################
+def init(bot):
+ global BOT
+ BOT = bot
+ bot.loop.create_task(clear_spam()) # set up a task for the bot to clear out spam from the
+ # txt file
+ row = db.select_query('SELECT * FROM spam_settings').fetchall()
+ if len(row) == 0:
+ # there is nothing in the database then put defaults in it
+ warning_num = 4 # number of messages before warning of spam
+ timeout_num = 5 # number of messages before timeout
+ timeout_min = 5 # number of minutes in timeout
+ timeout_hour = 0 # hours in timeout
+ timeout_day = 0 # days in timout
+ time_between_clears = 15
+ db.mutation_query(
+ 'INSERT INTO spam_settings VALUES (?, ?, ?, ?, ?, ?)',
+ [warning_num, timeout_num, timeout_min, timeout_hour, timeout_day, time_between_clears]
+ )
+
+###########################
+# Function: handle_spam
+# Description: takes a message and determines whether the author who sent that message is spamming
+# or not
+# Inputs:
+# - message: the message sent in the channel
+# - ctx: context of the message
+# - guild_id: the id of the guild we are in
+# Outputs: None
+###########################
+async def handle_spam(message, ctx, guild_id):
+ print(message.content)
+ print(message.author.id)
+ count = 0
+ with open("spam.txt", "a", encoding='utf-8') as f:
+ f.writelines(f"{str(message.author.id)}\n")
+
+ with open("spam.txt", "r+", encoding='utf-8') as f:
+ for line in f:
+ if line.strip("\n") == str(message.author.id):
+ count = count + 1
+ rows = db.select_query('SELECT * FROM spam_settings')
+ rows_tuple = rows.fetchall()[0]
+ warning_num = rows_tuple[0]
+ timeout_num = rows_tuple[1]
+ if count > timeout_num:
+ guild = BOT.get_guild(guild_id)
+ member = guild.get_member(message.author.id)
+ muted = discord.utils.get(guild.roles, name="Mute")
+ # await member.add_roles(muted)
+ seconds = 0
+ minutes = rows_tuple[2]
+ hours = rows_tuple[3]
+ days = rows_tuple[4]
+ await member.timeout(timedelta(seconds=seconds, minutes=minutes, hours=hours,
+ days=days))
+ await ctx.send(f"{message.author.name} has been muted") # lets the everyone know who
+ # was timed out
+ elif count > warning_num:
+ await ctx.send(f"Warning, {message.author.name} will be muted if they continue to spam")
diff --git a/src/spam.txt b/src/spam.txt
index 11c0aa10..e69de29b 100644
--- a/src/spam.txt
+++ b/src/spam.txt
@@ -1 +0,0 @@
-1150159208969408532
diff --git a/test/test_spam.py b/test/test_spam.py
index 77e9bae5..fb5547cb 100644
--- a/test/test_spam.py
+++ b/test/test_spam.py
@@ -3,6 +3,8 @@
###########################
from time import sleep
import discord
+from utils import wait_for_msg
+
###########################
# Function: test
# Description: runs each test
@@ -12,8 +14,110 @@
# Outputs: None
###########################
async def test(testing_bot, guild_id):
- await test_spam(testing_bot)
+ commands_channel = next(
+ ch for ch in testing_bot.get_guild(guild_id).text_channels if ch.name == 'instructor-commands')
+ # Add instructor role to bot
+ guild = testing_bot.get_guild(guild_id)
+ role = discord.utils.get(guild.roles, name="Instructor")
+ member = guild.get_member(testing_bot.user.id)
+ await member.add_roles(role) # so we can test the !set_spam_settings command
+
+ await start_invalid_settings(testing_bot, commands_channel)
+ await test_valid_settings(testing_bot, commands_channel)
+ #await test_invalid_timeout_time(testing_bot, commands_channel)
+ #await test_spam(testing_bot)
+
+async def start_invalid_settings(testing_bot, commands_channel):
+ await test_invalid_warning_num(testing_bot, commands_channel)
+ #await test_invalid_timeout_num(testing_bot, commands_channel)
+ #await test_invalid_clear_time(testing_bot, commands_channel)
+ #await test_invalid_timeout_time(testing_bot, commands_channel)
+
+
+async def test_invalid_warning_num(testing_bot, commands_channel):
+ await commands_channel.send('!set_spam_settings')
+ await wait_for_msg(testing_bot, commands_channel, 'How many messages should a user '
+ 'be able to send before they are warned of spamming?')
+ # above wait for the expected response, else it throws an exception in the function wait_for_msg
+
+ await commands_channel.send('90hoq03')
+ await wait_for_msg(testing_bot, commands_channel, 'Invalid entry. Try again...')
+
+ await test_invalid_timeout_num(testing_bot, commands_channel)
+
+
+
+async def test_invalid_timeout_num(testing_bot, commands_channel):
+ await commands_channel.send('!set_spam_settings')
+ await wait_for_msg(testing_bot, commands_channel, 'How many messages should a user '
+ 'be able to send before they are warned of spamming?')
+ # above wait for the expected response, else it throws an exception in the function wait_for_msg
+
+ await commands_channel.send('4')
+ await wait_for_msg(testing_bot, commands_channel, 'How many messages should a user '
+ 'be able to send before they are put in timeout?')
+ await commands_channel.send('+')
+ await wait_for_msg(testing_bot, commands_channel, 'Invalid entry. Try again...')
+
+ await test_invalid_clear_time(testing_bot, commands_channel)
+
+
+async def test_invalid_clear_time(testing_bot, commands_channel):
+ await commands_channel.send('!set_spam_settings')
+ await wait_for_msg(testing_bot, commands_channel, 'How many messages should a user '
+ 'be able to send before they are warned of spamming?')
+ # above wait for the expected response, else it throws an exception in the function wait_for_msg
+
+ await commands_channel.send('4')
+ await wait_for_msg(testing_bot, commands_channel, 'How many messages should a user '
+ 'be able to send before they are put in timeout?')
+ await commands_channel.send('5')
+ await wait_for_msg(testing_bot, commands_channel, 'How much time should users have to send 4 messages '
+ 'before they will be warned and then eventually put in timeout?')
+ await commands_channel.send('beezleopop')
+ await wait_for_msg(testing_bot, commands_channel, 'Invalid entry. Try again...')
+
+ await test_invalid_timeout_time(testing_bot, commands_channel)
+
+async def test_invalid_timeout_time(testing_bot, commands_channel):
+ await commands_channel.send('!set_spam_settings')
+ await wait_for_msg(testing_bot, commands_channel, 'How many messages should a user '
+ 'be able to send before they are warned of spamming?')
+ # above wait for the expected response, else it throws an exception in the function wait_for_msg
+
+ await commands_channel.send('4')
+ await wait_for_msg(testing_bot, commands_channel, 'How many messages should a user '
+ 'be able to send before they are put in timeout?')
+ await commands_channel.send('5')
+ await wait_for_msg(testing_bot, commands_channel, 'How much time should users have to send 4 messages '
+ 'before they will be warned and then eventually put in timeout?')
+ await commands_channel.send('15')
+ await wait_for_msg(testing_bot, commands_channel,
+ 'How long should a user be placed in timeout when caught spamming? '
+ 'Format: min,hours,days Example: 5,0,0 is 5 minutes')
+ await commands_channel.send('1')
+ await wait_for_msg(testing_bot, commands_channel, 'Invalid entry. Try again...')
+
+
+async def test_valid_settings(testing_bot, commands_channel):
+ await commands_channel.send('!set_spam_settings')
+ await wait_for_msg(testing_bot, commands_channel, 'How many messages should a user '
+ 'be able to send before they are warned of spamming?')
+ # above wait for the expected response, else it throws an exception in the function wait_for_msg
+
+ await commands_channel.send('4')
+ await wait_for_msg(testing_bot, commands_channel, 'How many messages should a user '
+ 'be able to send before they are put in timeout?')
+ await commands_channel.send('5')
+ await wait_for_msg(testing_bot, commands_channel, 'How much time should users have to send 4 messages '
+ 'before they will be warned and then eventually put in timeout?')
+ await commands_channel.send('15')
+ await wait_for_msg(testing_bot, commands_channel, 'How long should a user be placed in timeout when caught spamming? '
+ 'Format: min,hours,days Example: 5,0,0 is 5 minutes')
+ await commands_channel.send('5,0,0')
+ await wait_for_msg(testing_bot, commands_channel, 'Spam settings successfully updated!')
+"""
async def test_spam(testing_bot):
qna_channel = discord.utils.get(testing_bot.get_all_channels(), name='q-and-a')
await qna_channel.send('shit')
@@ -25,7 +129,7 @@ async def test_spam(testing_bot):
await ctx.send("message 6")
#messages = await qna_channel.history(limit=1).flatten()
messages = [message async for message in qna_channel.history(limit=1)]
-
+"""
diff --git a/test/tests.py b/test/tests.py
index 14023c39..acc6e95c 100644
--- a/test/tests.py
+++ b/test/tests.py
@@ -15,6 +15,7 @@
import test_help
import test_regrade
import test_email_address
+import test_spam
if platform.system() == 'Windows':
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
@@ -30,7 +31,6 @@ async def run_tests():
exit_status = 0
await begin_tests()
try:
-
print('testing QNA\n----------')
await test_qna.test(testing_bot, TEST_GUILD_ID)
print('testing office hours\n----------')
@@ -46,15 +46,15 @@ async def run_tests():
print('testing help\n----------')
await test_help.test(testing_bot, TEST_GUILD_ID)
print('testing regrade\n----------')
- #await test_regrade.test(testing_bot, TEST_GUILD_ID)
-
+ await test_regrade.test(testing_bot, TEST_GUILD_ID)
print('testing email address configuration\n----------')
await test_email_address.test(testing_bot, TEST_GUILD_ID)
print('testing chart\n-----------')
await test_chart.test(testing_bot, TEST_GUILD_ID)
-
print('testing email utility\n-----------')
#await test_email_utility.test()
+ print('testing spam\n----------')
+ await test_spam.test(testing_bot, TEST_GUILD_ID)
except AssertionError as ex:
print('exception: ', type(ex).__name__ + ':', ex)
print('--')