Compare commits

...
This repository has been archived on 2023-10-15. You can view files and clone it, but cannot push or open issues or pull requests.

13 Commits

17 changed files with 443 additions and 74 deletions

View File

@ -3,4 +3,7 @@ deployment:
tasks: tasks:
- export DEPLOYPATH=/home/benjamyntesting/chatbot/ - export DEPLOYPATH=/home/benjamyntesting/chatbot/
- cp -Rfv * $DEPLOYPATH - cp -Rfv * $DEPLOYPATH
- touch $DEPLOYPATH/tmp/restart.txt - touch $DEPLOYPATH/tmp/restart.txt
- source /home/benjamyntesting/virtualenv/chatbot/3.8/bin/activate && cd /home/benjamyntesting/chatbot
- export FLASK_APP=priceybot2
- flask db upgrade

24
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,24 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python: Flask",
"type": "python",
"request": "launch",
"module": "flask",
"env": {
"FLASK_APP": "priceybot2",
"FLASK_DEBUG": "1"
},
"args": [
"run",
"--host=0.0.0.0"
],
"jinja": true,
"justMyCode": true
}
]
}

View File

@ -30,35 +30,35 @@ def login_post():
return redirect(url_for('main.profile')) return redirect(url_for('main.profile'))
@auth.route('/signup') # @auth.route('/signup')
def signup(): # def signup():
return render_template('signup.html') # return render_template('signup.html')
@auth.route('/signup', methods=['POST']) # @auth.route('/signup', methods=['POST'])
def signup_post(): # def signup_post():
email = request.form.get('email') # email = request.form.get('email')
name = request.form.get('name') # name = request.form.get('name')
password = request.form.get('password') # password = request.form.get('password')
google_id = request.form.get('google_id') # google_id = request.form.get('google_id')
user = User.query.filter_by(email=email).first() # user = User.query.filter_by(email=email).first()
if user: # if user:
flash('Email already exists for user') # flash('Email already exists for user')
return redirect(url_for('auth.signup')) # return redirect(url_for('auth.signup'))
user = User.query.filter_by(google_id=google_id).first() # user = User.query.filter_by(google_id=google_id).first()
if user: # if user:
flash('Google ID already in use') # flash('Google ID already in use')
return redirect(url_for('auth.signup')) # return redirect(url_for('auth.signup'))
new_user = User(email=email, name=name, password=generate_password_hash(password, method='sha256'), google_id=google_id) # new_user = User(email=email, name=name, password=generate_password_hash(password, method='sha256'), google_id=google_id)
db.session.add(new_user) # db.session.add(new_user)
db.session.commit() # db.session.commit()
# Code to validate and add the user to the database # # Code to validate and add the user to the database
return redirect(url_for('auth.login')) # return redirect(url_for('auth.login'))
@auth.route('/logout') @auth.route('/logout')
@login_required @login_required

View File

@ -21,22 +21,29 @@ Hangouts Chat bot that responds to events and messages from a room asynchronousl
import logging import logging
import random
from flask import Blueprint, render_template, request, json from flask import Blueprint, render_template, request, json
from google.oauth2 import service_account from google.oauth2 import service_account
from googleapiclient.discovery import build from googleapiclient.discovery import build
from priceybot2 import models, db
logging.basicConfig(filename='example.log', level=logging.DEBUG) logging.basicConfig(filename='example.log', level=logging.DEBUG)
chatbot = Blueprint('chatbot', __name__) chatbot = Blueprint('chatbot', __name__)
scopes = ['https://www.googleapis.com/auth/chat.bot'] scopes = [
'https://www.googleapis.com/auth/chat.messages',
'https://www.googleapis.com/auth/chat.bot',
]
credentials = service_account.Credentials.from_service_account_file('./priceybot2/config/service-acct.json') credentials = service_account.Credentials.from_service_account_file('./priceybot2/config/service-acct.json')
# credentials, project_id = google.auth.default() # credentials, project_id = google.auth.default()
credentials = credentials.with_scopes(scopes=scopes) credentials = credentials.with_scopes(scopes=scopes)
chat = build('chat', 'v1', credentials=credentials) chat = build('chat', 'v1', credentials=credentials)
@chatbot.route('/bot/', methods=['POST']) @chatbot.route('/bot/', methods=['POST'])
def home_post(): def home_post():
"""Respond to POST requests to this endpoint. """Respond to POST requests to this endpoint.
@ -130,3 +137,65 @@ def home_get():
""" """
return render_template('home.html') return render_template('home.html')
@chatbot.route('/bot/members/<space>', methods=['GET'])
def members(space):
if space == "":
return {'text': "Please specify a space"}
try:
members = chat.spaces().members().list(parent=f'spaces/{space}').execute()
except Exception:
return {'text': "Space not found"}
for member in members['memberships']:
uid = int(member['member']['name'].split('/')[-1])
display_name = member['member']['displayName']
user = models.User.query.filter_by(google_id=uid).first()
if user:
print(f"User with UID of {uid} already exists in the DB")
continue
new_user = models.User(name=display_name, google_id=uid)
db.session.add(new_user)
print(f"Adding user {display_name}")
db.session.commit()
return {'text': "x"}
@chatbot.route('/bot/messages/<space>')
def get_messages(space):
messages = dir(chat.spaces().messages())
print(messages)
return "x"
@chatbot.route('/bot/randomquote/<space>', methods=['GET'])
def send_random_quote(space):
if space == "":
return {'text': "Please specify a space"}
try:
quote = random.choice(models.Quote.query.all())
except Exception:
return "Failed to get random quote"
chat.spaces().messages().create(
parent=f'spaces/{space}',
body={'text': quote.quote}
).execute()
return f"Sent random quote to space {space}"
@chatbot.route('/bot/sendquote/<space>', methods=['POST'])
def send_quote(space):
if request.json.get('quote_id') is None:
return "Fuck off"
quote = models.Quote.query.filter_by(id=request.json.get('quote_id')).first()
chat.spaces().messages().create(
parent=f'spaces/{space}',
body={'text': quote.quote}
).execute()
return "Sent quote"

View File

@ -1,5 +1,7 @@
from flask import Blueprint, render_template from flask import Blueprint, render_template
from flask_login import login_required, current_user from flask_login import login_required, current_user
from .models import Quote
from .chatbot import chat as bot
main = Blueprint('main', __name__) main = Blueprint('main', __name__)
@ -13,8 +15,20 @@ def index():
@login_required @login_required
def profile(): def profile():
return render_template( return render_template(
'profile.html', 'profile.html'
name=current_user.name,
google_id=current_user.google_id,
is_admin=current_user.administrator
) )
@main.route('/quotes')
@login_required
def quotes():
spaces = bot.spaces().list().execute()['spaces']
space_list = list()
for space in spaces:
if space.get('singleUserBotDm'):
continue
else:
space_list.append([space['name'].split('/')[1], space['displayName']])
quotes = [[q.quote, q.id] for q in Quote.query.all()]
return render_template("quotes.html", quotes=quotes, space_list=space_list)

View File

@ -1,7 +1,7 @@
from datetime import datetime from datetime import datetime
from flask_login import UserMixin from flask_login import UserMixin
from datetime import datetime
from . import db from . import db
@ -13,10 +13,19 @@ class User(UserMixin, db.Model):
google_id = db.Column(db.String(30), unique=True) google_id = db.Column(db.String(30), unique=True)
administrator = db.Column(db.Boolean, default=False) administrator = db.Column(db.Boolean, default=False)
class Quote(db.Model): class Quote(db.Model):
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
quote = db.Column(db.String(2000)) quote = db.Column(db.String(2000))
date_added = db.Column(db.DateTime(), default=datetime.now()) date_added = db.Column(db.DateTime(), default=datetime.now())
date_last_used = db.Column(db.DateTime()) date_last_used = db.Column(db.DateTime())
times_used = db.Column(db.Integer, default=0) times_used = db.Column(db.Integer, default=0)
class Credit(db.Model):
id = db.Column(db.Integer, primary_key=True)
amount = db.Column(db.Integer)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
class Transactions(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
quote_id = db.Column(db.Integer, db.ForeignKey('quote.id'))

View File

@ -5,8 +5,9 @@
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "mybulma",
"version": "1.0.0", "version": "1.0.0",
"license": "ISC", "license": "MIT",
"devDependencies": { "devDependencies": {
"bulma": "^0.9.4", "bulma": "^0.9.4",
"node-sass": "^8.0.0" "node-sass": "^8.0.0"

View File

@ -70,3 +70,14 @@ $link-focus-border: $nexi-darker-red;
//$link-active: $grey-darker !default //$link-active: $grey-darker !default
//$link-active-border: $grey-dark !default //$link-active-border: $grey-dark !default
$box-background-color: $nexi-grey;
$input-background-color: $nexi-darker-grey;
$input-color: $nexi-black;
// $table-background-color: $primary;
// $table-striped-row-even-background-color: $nexi-lighter-black;
// $table-color: $nexi-white;
$button-border-color: $nexi-darker-red;
$button-border-width: 1px;

View File

@ -36,4 +36,11 @@
.dark-toggle-animation { .dark-toggle-animation {
transition: background 500ms; transition: background 500ms;
}
.dark-position {
position: fixed;
bottom: 2em;
right: 2em;
} }

49
priceybot2/sass/flex.scss Normal file
View File

@ -0,0 +1,49 @@
.fcontainer {
display: flex;
flex-direction: column;
}
.innercontainer {
display: flex;
flex-direction: row;
background-color: #E4E4E4;
border-radius: 1em;
}
.innercontainer-dark {
display: flex;
flex-direction: row;
background-color: #353535;
border-radius: 1em;
}
.fquote {
display: flex;
flex-grow: 8;
}
.fbuttons {
display: flex;
align-self: flex-end;
}
.del-button {
border-color: #F44336 !important;
color: #F44336 !important;
}
.sel-button {
border-color: #353535 !important;
color: #353535 !important;
}
.del-button-dark {
border-color: #F44336 !important;
color: #F44336 !important;
}
.sel-button-dark {
border-color: #f8fafb !important;
color: #f8fafb !important;
}

View File

@ -3,6 +3,7 @@
// Our scss files // Our scss files
@import "branding"; @import "branding";
@import "dark-mode"; @import "dark-mode";
@import "flex";
// Import after our branding so colors work. // Import after our branding so colors work.
@import "../node_modules/bulma/bulma.sass"; @import "../node_modules/bulma/bulma.sass";

View File

@ -20,6 +20,51 @@
.dark-toggle-animation { .dark-toggle-animation {
transition: background 500ms; } transition: background 500ms; }
.dark-position {
position: fixed;
bottom: 2em;
right: 2em; }
.fcontainer {
display: flex;
flex-direction: column; }
.innercontainer {
display: flex;
flex-direction: row;
background-color: #E4E4E4;
border-radius: 1em; }
.innercontainer-dark {
display: flex;
flex-direction: row;
background-color: #353535;
border-radius: 1em; }
.fquote {
display: flex;
flex-grow: 8; }
.fbuttons {
display: flex;
align-self: flex-end; }
.del-button {
border-color: #F44336 !important;
color: #F44336 !important; }
.sel-button {
border-color: #353535 !important;
color: #353535 !important; }
.del-button-dark {
border-color: #F44336 !important;
color: #F44336 !important; }
.sel-button-dark {
border-color: #f8fafb !important;
color: #f8fafb !important; }
/*! bulma.io v0.9.4 | MIT License | github.com/jgthms/bulma */ /*! bulma.io v0.9.4 | MIT License | github.com/jgthms/bulma */
/* Bulma Utilities */ /* Bulma Utilities */
.button, .input, .textarea, .select select, .file-cta, .button, .input, .textarea, .select select, .file-cta,
@ -413,7 +458,7 @@ table th {
/* Bulma Elements */ /* Bulma Elements */
.box { .box {
background-color: white; background-color: #E4E4E4;
border-radius: 6px; border-radius: 6px;
box-shadow: 0 0.5em 1em -0.125em rgba(10, 10, 10, 0.1), 0 0px 0 1px rgba(10, 10, 10, 0.02); box-shadow: 0 0.5em 1em -0.125em rgba(10, 10, 10, 0.1), 0 0px 0 1px rgba(10, 10, 10, 0.02);
color: #181818; color: #181818;
@ -428,7 +473,7 @@ a.box:active {
.button { .button {
background-color: white; background-color: white;
border-color: #D2D2D2; border-color: #EB302D;
border-width: 1px; border-width: 1px;
color: #363636; color: #363636;
cursor: pointer; cursor: pointer;
@ -3660,18 +3705,18 @@ a.tag:hover {
/* Bulma Form */ /* Bulma Form */
.input, .textarea, .select select { .input, .textarea, .select select {
background-color: white; background-color: #D2D2D2;
border-color: #D2D2D2; border-color: #D2D2D2;
border-radius: 4px; border-radius: 4px;
color: #363636; } color: #181818; }
.input::-moz-placeholder, .textarea::-moz-placeholder, .select select::-moz-placeholder { .input::-moz-placeholder, .textarea::-moz-placeholder, .select select::-moz-placeholder {
color: rgba(54, 54, 54, 0.3); } color: rgba(24, 24, 24, 0.3); }
.input::-webkit-input-placeholder, .textarea::-webkit-input-placeholder, .select select::-webkit-input-placeholder { .input::-webkit-input-placeholder, .textarea::-webkit-input-placeholder, .select select::-webkit-input-placeholder {
color: rgba(54, 54, 54, 0.3); } color: rgba(24, 24, 24, 0.3); }
.input:-moz-placeholder, .textarea:-moz-placeholder, .select select:-moz-placeholder { .input:-moz-placeholder, .textarea:-moz-placeholder, .select select:-moz-placeholder {
color: rgba(54, 54, 54, 0.3); } color: rgba(24, 24, 24, 0.3); }
.input:-ms-input-placeholder, .textarea:-ms-input-placeholder, .select select:-ms-input-placeholder { .input:-ms-input-placeholder, .textarea:-ms-input-placeholder, .select select:-ms-input-placeholder {
color: rgba(54, 54, 54, 0.3); } color: rgba(24, 24, 24, 0.3); }
.input:hover, .textarea:hover, .select select:hover, .is-hovered.input, .is-hovered.textarea, .select select.is-hovered { .input:hover, .textarea:hover, .select select:hover, .is-hovered.input, .is-hovered.textarea, .select select.is-hovered {
border-color: #EB302D; } border-color: #EB302D; }
.input:focus, .textarea:focus, .select select:focus, .is-focused.input, .is-focused.textarea, .select select.is-focused, .input:active, .textarea:active, .select select:active, .is-active.input, .is-active.textarea, .select select.is-active { .input:focus, .textarea:focus, .select select:focus, .is-focused.input, .is-focused.textarea, .select select.is-focused, .input:active, .textarea:active, .select select:active, .is-active.input, .is-active.textarea, .select select.is-active {

View File

@ -55,18 +55,27 @@ function applyTheme(theme) {
// Find elements and update class. // Find elements and update class.
switch (theme) { switch (theme) {
case 'dark': case 'dark':
primaryElements = Array.from(document.querySelectorAll('.is-primary-invert')); test = Array('.is-primary-invert|is-primary', '.sel-button|sel-button-dark' , '.innercontainer|innercontainer-dark')
primaryElements.forEach((element) => { test.forEach((t) => {
element.classList.remove('is-primary-invert'); let c = t.split('|')
element.classList.add('is-primary'); primaryElements = Array.from(document.querySelectorAll(c[0]));
}); primaryElements.forEach((element) => {
element.classList.remove(c[0].split('.')[1]);
element.classList.add(c[1]);
});
})
break; break;
case 'light': case 'light':
primaryElements = Array.from(document.querySelectorAll('.is-primary')); test = Array('.is-primary|is-primary-invert', '.sel-button-dark|sel-button' , '.innercontainer-dark|innercontainer')
primaryElements.forEach((element) => { test.forEach((t) => {
element.classList.remove('is-primary'); let c = t.split('|')
element.classList.add('is-primary-invert'); primaryElements = Array.from(document.querySelectorAll(c[0]));
}); primaryElements.forEach((element) => {
element.classList.remove(c[0].split('.')[1]);
element.classList.add(c[1]);
});
})
break; break;
} }
} }

View File

@ -1,10 +1,11 @@
<!DOCTYPE html> <!DOCTYPE html>
<html class="is-clipped" xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Pricey Bot</title> <title>Pricey Bot</title>
<script src="https://kit.fontawesome.com/4b444ef337.js" crossorigin="anonymous"></script>
<script type="application/javascript" src="{{ url_for('static', filename='scripts/dark-mode.js', version='0.1') }}"></script> <script type="application/javascript" src="{{ url_for('static', filename='scripts/dark-mode.js', version='0.1') }}"></script>
<link rel="stylesheet" href="{{ url_for('static', filename='css/main.css', version='0.1') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='css/main.css', version='0.1') }}">
</head> </head>
@ -15,28 +16,49 @@
<nav class="navbar"> <nav class="navbar">
<div class="container"> <div class="container">
<div id="navbarMenuHeroA" class="navbar-menu"> <div id="navbarMenuHeroA" class="navbar-menu">
<div class="navbar-start">
{% if current_user.is_authenticated %}
<span class="navbar-item">{{ current_user.name }} is logged in{% if current_user.administrator %}<br/><span class="navbar-item icon p-4">
<i class="fas fa-screwdriver-wrench"></i>
</span></span>
{% endif %}
{% endif %}
</div>
<div class="navbar-end"> <div class="navbar-end">
{% if space_list is defined %}
<div class="select is-primary is-normal is-rounded m-4" >
<select name="space" id="space_option">
<option selected="selected" value="NaN">Please selelct a space!</option>
{% for i in space_list %}
<option value="{{ i[0] }}">{{ i[1] }}</option>
{% endfor %}
</select>
</div>
{% endif %}
<a href="{{ url_for('main.index') }}" class="navbar-item"> <a href="{{ url_for('main.index') }}" class="navbar-item">
Home Home
</a> </a>
{% if current_user.is_authenticated %} {% if current_user.is_authenticated %}
<a href="{{ url_for('main.profile') }}" class="navbar-item"> <a href="{{ url_for('main.profile') }}" class="navbar-item">
Profile Profile
</a> </a>
{% endif %} <a href="{{ url_for('main.quotes') }}" class="navbar-item">
{% if not current_user.is_authenticated %} Quotes
<a href="{{ url_for('auth.login') }}" class="navbar-item"> </a>
Login {% endif %}
</a> {% if not current_user.is_authenticated %}
<a href="{{ url_for('auth.signup') }}" class="navbar-item"> <a href="{{ url_for('auth.login') }}" class="navbar-item">
Sign Up Login
</a> </a>
{% endif %} {% endif %}
{% if current_user.is_authenticated %} {% if current_user.is_authenticated %}
<a href="{{ url_for('auth.logout') }}" class="navbar-item"> <a href="{{ url_for('auth.logout') }}" class="navbar-item">
Logout Logout
</a> </a>
{% endif %}
{% endif %}
</div> </div>
</div> </div>
</div> </div>
@ -52,9 +74,10 @@
<div class="hero-foot"> <div class="hero-foot">
<label class="is-pulled-right p-4"> <label class="is-pulled-right p-4">
<input name="dark-mode-toggle" class="dark-toggle" onclick="darkLight()" type="checkbox"> <input name="dark-mode-toggle" class="dark-toggle dark-position" onclick="darkLight()" type="checkbox">
</label> </label>
</div> </div>
</section> </section>
</body> </body>
</html> </html>

View File

@ -1,9 +1,23 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block content %} {% block content %}
<h1 class="title"> <div class="fcontainer is-primary has-text-left" style="flex-direction: column; align-content: flex-start">
Welcome, {{ name }}! <p/> <div style="display: flex; flex-grow: 2;">
Your google ID is {{ google_id }} <p/> <img src="https://benjamyn.love/frannodders.gif" width="256" height="256">
Admin?: {{ "yes" if is_admin else "no" }} <p/> </div>
</h1> <div style="display: flex; flex-grow: 8; flex-direction: column;">
<p>1</p>
<p>2</p>
<p>3</p>
<p>4</p>
<p>5</p>
<p>6</p>
</div>
<!-- <p class="fquote">Welcome, {{ current_user.name }}!</p>
<p class="fquote">Your google ID is {{ current_user.google_id }}</p>
<p class="fquote">Admin?: {{ "yes" if current_user.administrator else "no" }}</p> -->
</div>
{% endblock %} {% endblock %}

View File

@ -0,0 +1,40 @@
{% extends "base.html" %}
{% block content %}
<script>
function sendquote(id) {
select = document.getElementById("space_option");
space_id = select[select.selectedIndex].value;
space_name = select[select.selectedIndex].text;
if (space_id === "NaN") {
alert("Please select a space");
return
}
let confirmation = confirm(`Send quote to ${space_name}`)
if (confirmation === true) {
let send_url = "{{ url_for('chatbot.send_quote', space='AAAAAA', _external=True) }}"
send_url = send_url.replace("AAAAAA",space_id)
fetch(send_url, {method: "POST",headers: {"Content-Type": "application/json"},body: JSON.stringify({"quote_id": id})})
.then((response) => console.log(response))
}
}
</script>
<div class="fcontainer" >
{% for i in quotes %}
<div class=" has-text-left m-1 innercontainer p-4">
<div class="fquote" id="quote_{{ i[1] }}">
{{ i[0] }}
</div>
<div class="fbuttons">
<button class="button is-primary is-outlined m-1 sel-button" {% if not current_user.administrator %} disabled title="Administrator Privs required" {% endif %} onclick="sendquote({{ i[1] }})">Send</button>
<button class="button is-primary is-outlined m-1 del-button" {% if not current_user.administrator %} disabled title="Administrator Privs required" {% endif %} onclick="sendquote({{ i[1] }})">Delete</button>
</div>
</div>
{% endfor %}
</div>
{% endblock %}

View File

@ -0,0 +1,50 @@
{% extends "base.html" %}
{% block content %}
<script>
function sendquote(id) {
select = document.getElementById("space_option");
space_id = select[select.selectedIndex].value;
space_name = select[select.selectedIndex].text;
if (space_id === "NaN") {
alert("Please select a space");
return
}
let confirmation = confirm(`Send quote to ${space_name}`)
if (confirmation === true) {
let send_url = "{{ url_for('chatbot.send_quote', space='AAAAAA', _external=True) }}"
send_url = send_url.replace("AAAAAA",space_id)
fetch(send_url, {method: "POST",headers: {"Content-Type": "application/json"},body: JSON.stringify({"quote_id": id})})
.then((response) => console.log(response))
}
}
</script>
<div >
<h3 class="title">Quotes</h3>
<div >
{% with messages = get_flashed_messages() %}
{% if messages %}
<div class="notification is-danger">
{{ messages[0] }}
</div>
{% endif %}
{% endwith %}
<table class="table">
{% for quote in quotes %}
<tr>
<td class=" is-primary" style="text-align: left">
{{ quote[0] }}
</td>
{% if current_user.administrator %}
<td class="is-primary"><button class="is-primary button" onclick='sendquote({{ quote[1] }})' type="submit">Send</button></td>
{% endif %}
</tr>
{% endfor %}
</table>
</div>
</div>
{% endblock %}