Skip to content

Commit

Permalink
Merge next to master (#59)
Browse files Browse the repository at this point in the history
* Docker Support (#40)

* create sample env file

* delete sample env file

* added todo for me

* added instructions to run in docker too

* fix typos

* Ignore .idea/

* Removed todo

* Minor README edits

* Formatting; fixes #38

* README.md updates with Docker instructions

* Kubernetes documentation; closes #43

* Updated welcome question; closes #46

* Move files and update .gitignore

* Handle invalid tokens and allow dockerfile to only copy what is needed. Also split email into its own module (#53)

* handle no token and invalid token passed to the bot

* change docker to copy only needed folder

* Changed from string.format to fstring. Let black handle some formatting

* Move send_email into its own module. It works!

* Add hostname to INFO output.

* Updated text.

* Initial batch of DB functionality (#58)

* Populate first joined time, member ID, and permanent roles for existing users.

* Populate DB appropriately on join and on accept; add "myinfo" command

* Add join time and member info to joining users.

* Display permanent roles for users

* Actually add the Db class to git ಠ_ಠ

* Upsert new users instead of inserting.

* Readd permanent roles on join

* Add first join time to myinfo

* Update README and example

Co-authored-by: Mark <[email protected]>
Co-authored-by: Rahul Sundaresan <[email protected]>
Co-authored-by: rahul-sundaresan <[email protected]>
  • Loading branch information
4 people authored Jul 6, 2020
1 parent 2eef929 commit 7b1af61
Show file tree
Hide file tree
Showing 9 changed files with 385 additions and 47 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -201,4 +201,8 @@ fabric.properties

# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser

/.idea/

etc/*
!etc/kube-deploy.example.yaml
5 changes: 1 addition & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
FROM python:3.7

WORKDIR /usr/src/app

COPY requirements.txt ./
COPY network_ranger ./network_ranger
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

CMD [ "python3", "network_ranger" ]
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ SMTP_SERVER=in-v3.mailjet.com
SMTP_PORT=587
[email protected]
SECRETKEY=Secret key from Step 1
DB_HOST=mongodb.hostname
DB_PORT=27017
DB_NAME=network_ranger
DB_USER=<dbuser>
DB_PASS=<dbpass>
```
If you're running this directly instead of as a container, you will need to load each one of these as an environment
variable.
Expand Down
121 changes: 121 additions & 0 deletions etc/kube-deploy.example.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
apiVersion: v1
kind: Namespace
metadata:
name: network-ranger

---
kind: Deployment
apiVersion: apps/v1
metadata:
namespace: network-ranger
name: network-ranger
labels:
app: network-ranger

spec:
replicas: 1
selector:
matchLabels:
app: network-ranger
template:
metadata:
labels:
app: network-ranger
spec:
containers:
- name: network-ranger
image: netdiscord/network-ranger:latest
env:
- name: TOKEN
valueFrom:
secretKeyRef:
name: network-ranger
key: TOKEN
- name: BOT_DESCRIPTION
valueFrom:
secretKeyRef:
name: network-ranger
key: BOT_DESCRIPTION
- name: COMMAND_PREFIX
valueFrom:
secretKeyRef:
name: network-ranger
key: COMMAND_PREFIX
- name: LOGCHANNEL_NAME
valueFrom:
secretKeyRef:
name: network-ranger
key: LOGCHANNEL_NAME
- name: MEMBERCHANNEL_NAME
valueFrom:
secretKeyRef:
name: network-ranger
key: MEMBERCHANNEL_NAME
- name: MEMBERROLE_NAME
valueFrom:
secretKeyRef:
name: network-ranger
key: MEMBERROLE_NAME
- name: EGGSROLE_NAME
valueFrom:
secretKeyRef:
name: network-ranger
key: EGGSROLE_NAME
- name: WELCOMECHANNEL_NAME
valueFrom:
secretKeyRef:
name: network-ranger
key: WELCOMECHANNEL_NAME
- name: GUILD_NAME
valueFrom:
secretKeyRef:
name: network-ranger
key: GUILD_NAME
- name: MIRRORCHANNEL_NAME
valueFrom:
secretKeyRef:
name: network-ranger
key: MIRRORCHANNEL_NAME
- name: SMTP_USERNAME
valueFrom:
secretKeyRef:
name: network-ranger
key: SMTP_USERNAME
- name: SMTP_PASSWORD
valueFrom:
secretKeyRef:
name: network-ranger
key: SMTP_PASSWORD
- name: SMTP_SERVER
valueFrom:
secretKeyRef:
name: network-ranger
key: SMTP_SERVER
- name: SMTP_PORT
valueFrom:
secretKeyRef:
name: network-ranger
key: SMTP_PORT
- name: SMTP_FROMEMAIL
valueFrom:
secretKeyRef:
name: network-ranger
key: SMTP_FROMEMAIL
- name: SECRETKEY
valueFrom:
secretKeyRef:
name: network-ranger
key: SECRETKEY
- name: DB_HOST
value: "mongodb"
- name: DB_PORT
value: "27017"
- name: DB_USER
value: "root"
- name: DB_NAME
value: "network_ranger"
- name: DB_PASS
valueFrom:
secretKeyRef:
name: mongodb
key: mongodb-root-password
130 changes: 90 additions & 40 deletions network_ranger/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,36 @@
Discord bot to maintain the Networking Discord Server.
"""

import discord
from discord.ext import commands
import classes
from datetime import datetime
import asyncio
import subnet_calc
import smtplib, ssl
import json
import base64
from cryptography.fernet import Fernet, InvalidToken
import sys
import os
from datetime import datetime

import classes
import discord
import send_email
import subnet_calc
from cryptography.fernet import Fernet, InvalidToken
from discord.ext import commands

# import hashlib
from email_validator import validate_email, EmailNotValidError
from db import Db

conf = classes.Config()

if conf.get("db_name") and conf.get("db_user"):
db = Db(
host=conf.get("db_host"),
port=int(conf.get("db_port")),
mongo_user=conf.get("db_user"),
mongo_pass=conf.get("db_pass"),
dbname=conf.get("db_name"),
)
else:
db = False
print("DB configuration not present; not attempting to connect to DB.")

bot = commands.Bot(
command_prefix=conf.get("command_prefix"),
description=conf.get("bot_description"),
Expand All @@ -48,24 +61,6 @@
)


async def send_email(to_email, message):
smtp_server = conf.get("smtp_server")
port = conf.get("smtp_port")
username = conf.get("smtp_username")
password = conf.get("smtp_password")
from_email = conf.get("smtp_fromemail")
context = ssl.create_default_context()
try:
server = smtplib.SMTP(smtp_server, port)
server.starttls(context=context)
server.login(username, password)
server.sendmail(from_email, to_email, message)
except Exception as e:
print(e)
finally:
server.quit()


async def clear_member_roles(member, roletype: str):
for role in member.roles:
if role.name.startswith(roletype + ":"):
Expand Down Expand Up @@ -160,6 +155,9 @@ async def on_ready():
username=bot.user.name, userid=bot.user.id
)
)
if db:
db.add_existing_members(bot.guilds[0])

# TODO: De-hardcode
global welcomechannel
welcomechannel = discord.utils.get(
Expand Down Expand Up @@ -234,18 +232,40 @@ async def on_ready():

@bot.command(help="Shows bot information")
@commands.check(is_guild_mod)
async def info(ctx):
async def botinfo(ctx):
embed = discord.Embed(
title="Network Ranger", description=conf.get("bot_description")
)
embed.add_field(name="Host", value=os.uname()[1])
embed.add_field(name="Command Prefix", value=conf.get("command_prefix"))
embed.add_field(
name="Github", value="https://github.com/networking-discord/network-ranger"
)
await ctx.send(embed=embed)


@bot.command(help="Shows your member profile")
@commands.check(is_accepted)
async def myinfo(ctx):
embed = discord.Embed(
title=ctx.author.display_name,
description=f"{ctx.author.name}#{ctx.author.discriminator}",
)
member_info = db.get_member(ctx.author.id)
embed.add_field(name="Member Number", value=member_info["member_number"])
embed.add_field(
name="First Joined At",
value=datetime.utcfromtimestamp(member_info["first_joined_at"]).strftime(
"%Y-%b-%d %H:%M:%S UTC"
),
)
permanent_roles = "\r\n".join(db.get_permanent_roles(ctx.author.id))
embed.add_field(name="Permanent Roles", value=permanent_roles)
await ctx.send(embed=embed)


@bot.command(help="Send an email key")
@commands.check(is_accepted)
async def sendkey(ctx, email: str):
# Delete the message if it hasn't already been deleted.
try:
Expand All @@ -267,11 +287,9 @@ async def sendkey(ctx, email: str):
email = valid.email
domain = valid.domain
secretkey = conf.get("secretkey").encode()

# Calculate an encrypted JSON string with userid and email
emailkey = json.dumps({"uid": str(ctx.author.id), "email": email})
emailkey = Fernet(secretkey).encrypt(emailkey.encode())

msg = """\
To: {email}
Subject: Networking Discord Email Validation Key
Expand All @@ -285,9 +303,10 @@ async def sendkey(ctx, email: str):
key=emailkey.decode(),
command_prefix=conf.get("command_prefix"),
)
await send_email(email, msg)
await send_email.send_email(email, msg)
await ctx.send(
"{mention}: I've emailed you to check your association with {domain}. Please check your email for the validation instructions.".format(
"{mention}: I've emailed you to check your association with {domain}. "
"Please check your email for the validation instructions.".format(
domain=domain, mention=ctx.author.mention
)
)
Expand Down Expand Up @@ -460,21 +479,24 @@ async def accept(ctx, answer: str = None):
await ctx.author.add_roles(
memberrole, reason="Accepted rules; Answer: " + answer
)
db.add_permanent_role(ctx.author.id, "Member")
db.add_member_numbers()
await memberchannel.send(
"{mention}, welcome to {server}! You are member #{membernumber}, and we're glad to have you. Feel free to "
"take a moment to introduce yourself! If you want to rep your company or school based on your email domain,"
" get a key by DMing me the command ```{command_prefix}sendkey <email>``` then set an org role using: "
"```{command_prefix}role org set <key>``` in any channel".format(
mention=ctx.author.mention,
server=memberchannel.guild.name,
membernumber=len(memberrole.members),
membernumber=db.get_member_number(ctx.author.id),
command_prefix=conf.get("command_prefix"),
)
)
elif answer == "eggs":
await ctx.author.add_roles(
eggsrole, reason="Really, terribly, desperately addicted to eggs."
)
db.add_permanent_role(ctx.author.id, "!eggs")
response = (
"*****{mention}, congratulations! You've joined {eggsmention}! For more information about eggs, please "
"visit https://lmgtfy.com/?q=eggs or consult your local farmer.".format(
Expand All @@ -498,13 +520,32 @@ async def on_member_join(member):
:param member:
:return:
"""
await welcomechannel.send(
conf.get("welcomemessage").format(
server=member.guild.name,
mention=member.mention,
command_prefix=conf.get("command_prefix"),
db.add_member(member)
permanent_roles = db.get_permanent_roles(member.id)
if "!eggs" in permanent_roles:
# Reapply !eggs role if they had it before
await member.add_roles(eggsrole, reason="Eggs have returned.")
if "Member" in permanent_roles:
# Bypass welcome channel
await member.add_roles(memberrole)
await memberchannel.send(
"{mention}, welcome back to {server}! We've held on to your previous member number,"
" #{membernumber}.".format(
mention=member.mention,
server=memberchannel.guild.name,
membernumber=db.get_member_number(member.id),
)
)
else:
# Send to welcome channel
db.add_first_joined_ats(bot.guilds[0])
await welcomechannel.send(
conf.get("welcomemessage").format(
server=member.guild.name,
mention=member.mention,
command_prefix=conf.get("command_prefix"),
)
)
)


@bot.event
Expand All @@ -523,4 +564,13 @@ async def on_message(message):
await asyncio.create_task(bot.process_commands(message))


bot.run(conf.get("token"))
if __name__ == "__main__":

token: str = conf.get("token")
if token is None:
print("Fatal: Discord bot token not set. Exiting")
sys.exit(1)
try:
bot.run(token)
except discord.errors.LoginFailure as login_failure:
sys.exit(str(login_failure))
Loading

0 comments on commit 7b1af61

Please sign in to comment.