Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add wildcard support for email domains #4

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
23 changes: 9 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,12 @@
<p align="center">
<img src="docs/emailbot.png" />
</p>

**EmailBot** allows you to verify that your Discord server members have an email address with a specific domain.

[![Discord](https://img.shields.io/discord/731028346569228288)](https://discord.gg/MfFMxu9) [![License](https://img.shields.io/badge/license-GPL-brightgreen)](LICENSE)

> Invite: https://discord.com/api/oauth2/authorize?client_id=731027450607435846&permissions=268503040&scope=bot

> Discord server: https://discord.gg/MfFMxu9

## Usage

After [inviting](https://discord.com/api/oauth2/authorize?client_id=731027450607435846&permissions=268503040&scope=bot) the bot to your server, a domain must be added using `.dominadd domain`. `.vstatus` is the help command:
After inviting the bot to your server, a domain must be added using `.dominadd domain`. `.vstatus` is the help command:

```
User commands:
.verify -> Sends a DM to the user to verify their email
.vstatus -> This help message
.unverify -> unverify self

Admin commands:
- A domain must be added before users can be verified.
Expand All @@ -35,7 +24,7 @@ Verified role (default=Verified): Verified

## Example

Let's say you want a Discord server just for people who have a @randomuniversity.edu email address. Add this bot to your server and when someone joins, they will get a DM asking for their @randomuniversity.edu email address. The bot then emails them a verification code. If they reply with the correct code, they get the "Verified" role.
Let's say you want a Discord server just for people who have a @randomuniversity.edu email address. Add this bot to your server and when someone joins, they will get a DM asking for their @randomuniversity.edu email address. The bot then emails them a verification code. If they reply with the correct code, they get the "Verified" role. Wildcard can be used for email domain, such as *.randomuniversity.edu

<p align="center">
<img src="docs/screenshot.png" />
Expand All @@ -51,12 +40,18 @@ pip install -r requirements.txt

Before running it make sure these environment variables are set. You will need a [Sendgrid](https://sendgrid.com/docs/for-developers/sending-email/api-getting-started/) and [Discord](https://discordpy.readthedocs.io/en/latest/discord.html#discord-intro) account (both are free). Optionally consider making a [Mailgun](https://documentation.mailgun.com/en/latest/quickstart-sending.html#how-to-start-sending-email) account, since this bot uses Mailgun when Sendgrid fails to send an email:

SMTP will be used first, if failed, Sendgrid, if failed again, Mailgun. Set SMTP_USE_STARTTLS to 'true' if using STARTTLS
```
export SENDGRID_API_KEY='YOUR_SENDGRID_API_KEY'
export SENDGRID_EMAIL='YOUR_SENDGRID_EMAIL'
export DISCORD_TOKEN='YOUR_DISCORD_TOKEN'
export MAILGUN_API_KEY='YOUR_MAILGUN_API_KEY'
export MAILGUN_DOMAIN='YOUR_MAILGUN_DOMAIN'
export 'SMTP_USER'='YOUR_SMTP_EMAIL_ADDRESS'
export 'SMTP_SERVER'='YOUR_SMTP_SERVER'
export 'SMTP_PORT'='YOUR_SMTP_SERVER_PORT'
export 'SMTP_PASSWORD'='YOUR_SMTP_PASSWORD'
export 'SMTP_USE_STARTTLS'='true'
```

Run the bot with:
Expand Down
229 changes: 154 additions & 75 deletions bot.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import typing
import discord
from discord.ext import commands
import sqlite3
Expand All @@ -8,6 +9,9 @@
from sendgrid.helpers.mail import Mail
import requests
from keep_alive import keep_alive
import fnmatch
import smtplib
from email.mime.text import MIMEText

conn = sqlite3.connect('bot.db')
c = conn.cursor()
Expand Down Expand Up @@ -59,6 +63,10 @@ def verify_user(userid, guildid):
c.execute("UPDATE users SET verified=1 WHERE userid=? AND guildid=?", (userid, guildid))
conn.commit()

def unverify_user(userid, guildid):
c.execute("UPDATE users SET verified=0 WHERE userid=? AND guildid=?", (userid, guildid))
conn.commit()

def get_domains(guildid):
return get_guild(guildid)[1]

Expand Down Expand Up @@ -115,6 +123,29 @@ def mailgun_send(email_address, verification_code):
"subject": "Verify your server email",
"text": str(verification_code)})

def domain_wildcard_match(domain_to_verify, domains_allowed):
for da in domains_allowed:
if fnmatch.fnmatch(domain_to_verify, da):
return True
return False

def smtp_send(email_address, verification_code):
user_email = os.environ.get('SMTP_USER')
smtp_server = os.environ.get('SMTP_SERVER')
smtp_port = int(os.environ.get('SMTP_PORT'))
smtp_pswd = os.environ.get('SMTP_PASSWORD')
smtp_use_starttls = os.environ.get('SMTP_USE_STARTTLS') == 'true'
with smtplib.SMTP(smtp_server,smtp_port) as sv:
if smtp_use_starttls:
sv.starttls()
sv.login(user_email, smtp_pswd)
receiver = email_address
msg = MIMEText(f'Your verification code is {verification_code}. Please reply to EmailBot in the discord server')
msg['Subject'] = 'Verify your discord server email'
msg['From'] = user_email
msg['To'] = receiver
sv.sendmail(user_email, receiver, msg.as_string())

intents = discord.Intents.default()
intents.members = True

Expand Down Expand Up @@ -147,85 +178,115 @@ async def on_member_join(member):
role = discord.utils.get(member.guild.roles, name=check_on_join[3])
await member.add_roles(role)

@client.event
async def on_message(message):
if message.author == client.user:
return
message_content = message.content.strip()
if (message.guild == None) and email_check(message_content):
users_guilds = get_users_guilds(message.author.id)
if len(users_guilds) >= 1:
guild_list = [i[1] for i in users_guilds if i[4] == 0]
verif_list = []
for i in guild_list:
email_guild = get_emails_guilds(i, message_content)
if len(email_guild) > 0:
continue
guild_domains = get_domains(i)
if len(guild_domains) == 0:
continue
guild_domains = guild_domains.split('|')
if message_content.split("@")[1] in guild_domains:
verif_list.append(i)
if len(verif_list) >= 1:
random_code = random.randint(100000, 999999)
for i in verif_list:
insert_code(random_code, message.author.id, i)
insert_email(message_content, message.author.id, i)
emailmessage = Mail(
from_email=os.environ.get('SENDGRID_EMAIL'),
to_emails=message_content,
subject='Verify your server email',
html_content=str(random_code))
try:
sg = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY'))
response = sg.send(emailmessage)
print(response.status_code)
print(response.body)
print(response.headers)
await message.channel.send("Email sent. **Reply here with your verification code**. If you haven't received it, check your spam folder.")
except Exception as e:
mailgun_email = mailgun_send(message_content, random_code)
if mailgun_email.status_code == 200:
await message.channel.send("Email sent. **Reply here with your verification code**. If you haven't received it, check your spam folder.")
else:
await message.channel.send("Email failed to send.")
else:
await message.channel.send("Invalid email.")
class MessageCog(commands.Cog):
def __init__(self, bot):
self.bot = bot
self._cd = commands.CooldownMapping.from_cooldown(1, 6.0, commands.BucketType.member) # Change accordingly
# rate, per, BucketType

def get_ratelimit(self, message: discord.Message) -> typing.Optional[int]:
"""Returns the ratelimit left"""
bucket = self._cd.get_bucket(message)
return bucket.update_rate_limit()

@commands.Cog.listener()
async def on_message(self, message):
if message.guild:
return
if message.author == client.user:
return
# Getting the ratelimit left
ratelimit = self.get_ratelimit(message)
print(f'Rate limit: {ratelimit}')
if ratelimit is None:
# The user is not ratelimited, you can add the XP or level up the user here
pass
else:
await message.channel.send("You have not joined a server.")
elif (len(message_content) == 6) and message_content.isdigit():
verif_code = int(message_content)
prev_codes_f = get_users_codes(message.author.id, verif_code)
prev_codes_g = [i for i in prev_codes_f if i[4] == 0]
prev_codes = []
for i in prev_codes_g:
user_emails = get_emails_guilds(i[1], i[2])
if len(user_emails) >= 1:
continue
await message.channel.send("You are rate-limited. Please try again later.")
return

message_content = message.content.strip()
if (message.guild == None) and email_check(message_content):
users_guilds = get_users_guilds(message.author.id)
if len(users_guilds) >= 1:
guild_list = [i[1] for i in users_guilds if i[4] == 0]
verif_list = []
for i in guild_list:
email_guild = get_emails_guilds(i, message_content)
if len(email_guild) > 0:
continue
guild_domains = get_domains(i)
if len(guild_domains) == 0:
continue
guild_domains = guild_domains.split('|')
if domain_wildcard_match(message_content.split("@")[1],guild_domains):
verif_list.append(i)
if len(verif_list) >= 1:
random_code = random.randint(100000, 999999)
for i in verif_list:
insert_code(random_code, message.author.id, i)
insert_email(message_content, message.author.id, i)
emailmessage = Mail(
from_email=os.environ.get('SENDGRID_EMAIL'),
to_emails=message_content,
subject='Verify your server email',
html_content=str(random_code))
try :
smtp_send(message_content, random_code)
print('Email sent')
await message.channel.send("Email sent. **Reply here with your verification code**. If you haven't received it, check your spam folder.")
except Exception as e:
try:
sg = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY'))
response = sg.send(emailmessage)
print(response.status_code)
print(response.body)
print(response.headers)
await message.channel.send("Email sent. **Reply here with your verification code**. If you haven't received it, check your spam folder.")
except Exception as ee:
mailgun_email = mailgun_send(message_content, random_code)
if mailgun_email.status_code == 200:
await message.channel.send("Email sent. **Reply here with your verification code**. If you haven't received it, check your spam folder.")
else:
await message.channel.send("Email failed to send.")
else:
await message.channel.send("Invalid email.")
else:
prev_codes.append(i)
if len(prev_codes) >= 1:
for i in prev_codes:
verify_user(message.author.id, i[1])
curr_guild = client.get_guild(i[1])
guild_db = get_guild(i[1])
role = discord.utils.get(curr_guild.roles, name=guild_db[3])
if role:
member = curr_guild.get_member(message.author.id)
if role not in member.roles:
await member.add_roles(role)
await message.channel.send("You have not joined a server.")
elif (len(message_content) == 6) and message_content.isdigit():
verif_code = int(message_content)
prev_codes_f = get_users_codes(message.author.id, verif_code)
prev_codes_g = [i for i in prev_codes_f if i[4] == 0]
prev_codes = []
for i in prev_codes_g:
user_emails = get_emails_guilds(i[1], i[2])
if len(user_emails) >= 1:
continue
else:
await curr_guild.create_role(name=guild_db[3])
prev_codes.append(i)
if len(prev_codes) >= 1:
for i in prev_codes:
verify_user(message.author.id, i[1])
curr_guild = client.get_guild(i[1])
guild_db = get_guild(i[1])
role = discord.utils.get(curr_guild.roles, name=guild_db[3])
member = curr_guild.get_member(message.author.id)
await member.add_roles(role)
await message.channel.send("You have been verified on " + client.get_guild(i[1]).name + ".")
else:
await message.channel.send("Incorrect code.")
elif message.guild == None:
await message.channel.send("Invalid email.")
await client.process_commands(message)
if role:
member = curr_guild.get_member(message.author.id)
if role not in member.roles:
await member.add_roles(role)
else:
await curr_guild.create_role(name=guild_db[3])
role = discord.utils.get(curr_guild.roles, name=guild_db[3])
member = curr_guild.get_member(message.author.id)
await member.add_roles(role)
await message.channel.send("You have been verified on " + client.get_guild(i[1]).name + ".")
else:
await message.channel.send("Incorrect code.")
elif message.guild == None:
await message.channel.send("Invalid email.")
# await client.process_commands(message)

client.add_cog(MessageCog(client))

@client.event
async def on_guild_join(guild):
Expand Down Expand Up @@ -302,6 +363,7 @@ async def vstatus(ctx):
new_guild(ctx.guild.id)
check_on_join = get_guild(ctx.guild.id)
on_join = bool(check_on_join[2])
print('request vstatus')
await ctx.send("```" +
"Ping: " + "{0}ms".format(round(client.latency * 1000)) + "\n" +
"User commands: " + "\n" +
Expand Down Expand Up @@ -337,5 +399,22 @@ async def verify(ctx):
elif user_prev_verify[4] == 0:
await ctx.author.send(verify_msg(ctx.guild, check_on_join[1]))

@client.command()
async def unverify(ctx):
if ctx.guild:
check_on_join = get_guild(ctx.guild.id)
if check_on_join == None:
new_guild(ctx.guild.id)
unverify_user(ctx.author.id,ctx.guild.id)
insert_email("", ctx.author.id, ctx.guild.id)
curr_guild = client.get_guild(ctx.guild.id)
guild_db = get_guild(ctx.guild.id)
role = discord.utils.get(curr_guild.roles, name=guild_db[3])
member = curr_guild.get_member(ctx.author.id)
await member.remove_roles(role)
await ctx.author.send(f'You have been unverified on {curr_guild.name}.')



keep_alive()
client.run(os.environ.get('DISCORD_TOKEN'))
Loading