Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ venv
.env/

# config
config.json
config.yaml
discord.log

# vscode
Expand Down
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2019 Will Russell and Justin Chadwell

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
22 changes: 11 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,17 @@ $ pip3 install -r requirements.txt

You will need a token for discord. Follow [this guide](https://github.com/reactiflux/discord-irc/wiki/Creating-a-discord-bot-&-getting-a-token) to get one.

Add the token, and URL for the form you want to use to `config.json`.

```json
{
"token": "token",
"url": "url",
"start_message": "First message when the command is called",
"end_message": "Last command to show all the questions have be answered",
"embed_title": "The title for polls, such as radios",
"prefix": "prefix for the discord bot"
}
Add the token, and URL for the form you want to use to `config.yaml`.

```yaml
url:

discord:
token:
start_message: "Hello there! I'm here to help"
end_message: "Someone will be over to help you shortly!"
embed_title: "Respond with one of the options below"
prefix: "!"
```

### Run
Expand Down
8 changes: 0 additions & 8 deletions config.json

This file was deleted.

8 changes: 8 additions & 0 deletions config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
url:

discord:
token:
start_message: "Hello there! I'm here to help"
end_message: "Someone will be over to help you shortly!"
embed_title: "Respond with one of the options below"
prefix: "!"
4 changes: 0 additions & 4 deletions formbot/fields.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import re


class Field:
def __init__(self, type, name, display=None, required=False, default=None,
validator=None, extra=None):
Expand Down Expand Up @@ -42,7 +41,6 @@ def __str__(self):

return result


def email(data, field):
# init static variables
if not hasattr(email, 'EMAIL_REGEX'):
Expand All @@ -54,7 +52,6 @@ def email(data, field):
else:
raise ValueError('invalid email address')


def checkbox(data, field):
# init static variables
if not hasattr(checkbox, 'TRUE_VALUES'):
Expand All @@ -76,7 +73,6 @@ def checkbox(data, field):
else:
return None


def radio(data, field):
data = data.lower()

Expand Down
60 changes: 60 additions & 0 deletions formbot/form.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import requests

class Form:
def __init__(self, session, method, action):
self.session = session
self.method = method.upper()
self.action = action

self.fields = []
self.name_lookup = {}
self.id_lookup = {}

def add_field(self, field, id=None):
if field.name in self.name_lookup:
raise ValueError('cannot have duplicate field names')
else:
self.fields.append(field)
self.name_lookup[field.name] = field
if id:
self.id_lookup[id] = field

def get_field(self, name=None, id=None):
if name and id:
raise ValueError('cannot get by both name and id')
elif name:
return self.name_lookup[name]
elif id:
return self.id_lookup[id]
else:
raise ValueError('missing search specifier (should be name or id)')

def fill_field(self, name, value):
if name not in self.name_lookup:
raise KeyError(f'{name} does not appear in form')

field = self.name_lookup[name]
field.fill(value)

def submit(self):
# populate values
values = {}
for field in self.fields:
if field.required and field.data is None:
raise KeyError(
f'{field.name} is required and has not been provided')

if field.data is not None:
values[field.name] = field.data
elif field.type == 'hidden':
values[field.name] = field.data or ''

# send form
req = requests.Request(self.method, self.action, data=values)
resp = self.session.send(req.prepare())

# check for submission errors
if resp.status_code >= 400 and resp.status_code < 500:
raise RuntimeError('invalid request during form submission')
if resp.status_code >= 500 and resp.status_code < 600:
raise RuntimeError('internal server error during form submission')
67 changes: 35 additions & 32 deletions formbot/formbot.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
import discord
from discord.ext import commands
import json
import logging
import requests
import yaml
from .scraper import FormScraper

with open("config.json") as file:
config = json.load(file)
with open("config.yaml") as file:
config = yaml.load(file, Loader=yaml.BaseLoader)

bot = commands.Bot(command_prefix=config['discord']['prefix'])

bot = commands.Bot(command_prefix=config['prefix'])
scaper_obj = FormScraper(config['url'])

responses = {}
questions = {}


def main():
logging.basicConfig(level=logging.INFO)
token = config['token']
token = config['discord']['token']
bot.run(token)


def get_questions(form):
fields = form.fields
questions = []
Expand All @@ -31,63 +31,62 @@ def get_questions(form):
elif field.type == 'radio':
items = field.display.split(',')
embed = discord.Embed(
title=config['embed_title'], colour=0x348DDD)
title=config['discord']['embed_title'], colour=0x348DDD)
for index, item in enumerate(items):
option = "Option " + str(index + 1)
embed.add_field(name=item, value=option, inline=False)
questions.append(embed)
return questions


@bot.event
async def on_ready():
print("Ready")


@bot.event
async def on_message(message):
if message.author.bot:
return
await bot.process_commands(message)
if message.guild is None:
ctx = await bot.get_context(message)

if ctx.valid:
print('Command')
await bot.process_commands(message)
elif message.channel.type == discord.ChannelType.private:
print("DM channel")
author = str(message.author.id)
if author in responses and len(responses[author]['responses']) < \
len(questions[author]['questions']):
if author in responses and len(responses[author]['responses']) < len(questions[author]['questions']):
print("Message: " + str(message.clean_content))
handle_response(message, author)
await mentor_response(message)
print(responses)

def handle_response(response, author):
responses[author]['responses'].append(response.content)
index = len(responses[author]['responses']) - 1
name = questions[author]['names'][index]
responses[author]['form'].fill_field(name, response.content)
print("Response added to field")

async def mentor_response(message):
author = str(message.author.id)
response_length = len(responses[author]['responses'])

if response_length < len(questions[author]['questions']):
if isinstance(questions[author]['questions'][response_length], str):
await message.author.send(
questions[author]['questions'][response_length])
await message.author.send(questions[author]['questions'][response_length])
else:
await message.author.send(
embed=questions[author]['questions'][response_length])
await message.author.send(embed=questions[author]['questions'][response_length])
else:
await message.author.send(config['end_message'])
await message.author.send(config['discord']['end_message'])
responses[author]['form'].submit()
del responses[author]
del questions[author]


def handle_response(response, author):
responses[author]['responses'].append(response.content)
index = len(responses[author]['responses']) - 1
name = questions[author]['names'][index]
responses[author]['form'].fill_field(name, response.content)
print("Response added to field")


@bot.command()
async def mentor(ctx):
form = scaper_obj.extract()
session = requests.session()
form = scaper_obj.extract(session)
author = str(ctx.message.author.id)

responses[author] = {
"form": form,
"responses": []
Expand All @@ -96,7 +95,11 @@ async def mentor(ctx):
"questions": get_questions(form),
"names": [field.name for field in form.fields if not field.hidden]
}

print(questions[author])
await ctx.author.send(config['start_message'])
await ctx.author.send(config['discord']['start_message'])

await mentor_response(ctx.message)
await ctx.message.delete()
if ctx.message.channel.type != discord.ChannelType.private:
await ctx.message.delete()
print("Mentoring session started with {}".format(ctx.message.author.name))
Loading