diff --git a/setup.py b/setup.py
index 9fe8b0c..5b34277 100644
--- a/setup.py
+++ b/setup.py
@@ -2,7 +2,7 @@ from setuptools import find_packages, setup
setup(
name='tlapbot',
- version='0.5.3',
+ version='0.6.0',
packages=find_packages(),
include_package_data=True,
install_requires=[
diff --git a/tlapbot/__init__.py b/tlapbot/__init__.py
index bc19be3..c5a9368 100644
--- a/tlapbot/__init__.py
+++ b/tlapbot/__init__.py
@@ -4,6 +4,7 @@ from apscheduler.schedulers.background import BackgroundScheduler
from tlapbot.db import get_db
from tlapbot.owncast_helpers import is_stream_live, give_points_to_chat
+
def create_app(test_config=None):
app = Flask(__name__, instance_relative_config=True)
@@ -14,20 +15,21 @@ def create_app(test_config=None):
pass
# Prepare config: set db to instance folder, then load default, then
- # overwrite it with config.py
+ # overwrite it with config.py and redeems.py
app.config.from_mapping(
DATABASE=os.path.join(app.instance_path, "tlapbot.sqlite")
)
app.config.from_object('tlapbot.default_config')
+ app.config.from_object('tlapbot.default_redeems')
app.config.from_pyfile('config.py')
-
+ app.config.from_pyfile('redeems.py')
# prepare webhooks and redeem dashboard blueprints
from . import owncast_webhooks
from . import owncast_redeem_dashboard
app.register_blueprint(owncast_webhooks.bp)
app.register_blueprint(owncast_redeem_dashboard.bp)
-
+
# add db initialization CLI command
from . import db
db.init_app(app)
@@ -44,12 +46,11 @@ def create_app(test_config=None):
# start scheduler that will give points to users
points_giver = BackgroundScheduler()
- points_giver.add_job(proxy_job, 'interval', seconds=app.config['POINTS_CYCLE_TIME']) # change to 10 minutes out of testing
+ points_giver.add_job(proxy_job, 'interval', seconds=app.config['POINTS_CYCLE_TIME'])
points_giver.start()
return app
-
if __name__ == '__main__':
- create_app()
\ No newline at end of file
+ create_app()
diff --git a/tlapbot/db.py b/tlapbot/db.py
index 74dea73..8bb5b68 100644
--- a/tlapbot/db.py
+++ b/tlapbot/db.py
@@ -22,12 +22,28 @@ def close_db(e=None):
if db is not None:
db.close()
+
+def insert_counters(db):
+ for redeem, redeem_info in current_app.config['REDEEMS'].items():
+ if redeem_info["type"] == "counter":
+ try:
+ cursor = db.execute(
+ "INSERT INTO counters(name, count) VALUES(?, 0)",
+ (redeem,)
+ )
+ db.commit()
+ except Error as e:
+ print("Failed inserting counters to db:", e.args[0])
+
+
def init_db():
db = get_db()
with current_app.open_resource('schema.sql') as f:
db.executescript(f.read().decode('utf8'))
+ insert_counters(db)
+
@click.command('init-db')
@with_appcontext
@@ -36,6 +52,7 @@ def init_db_command():
init_db()
click.echo('Initialized the database.')
+
def init_app(app):
app.teardown_appcontext(close_db)
- app.cli.add_command(init_db_command)
\ No newline at end of file
+ app.cli.add_command(init_db_command)
diff --git a/tlapbot/default_redeems.py b/tlapbot/default_redeems.py
new file mode 100644
index 0000000..6956e1c
--- /dev/null
+++ b/tlapbot/default_redeems.py
@@ -0,0 +1,6 @@
+REDEEMS={
+ "hydrate": {"price": 60, "type": "list"},
+ "lurk": {"price": 1, "type": "counter"},
+ "react": {"price": 200, "type": "note"},
+ "request": {"price": 100, "type": "note"}
+}
\ No newline at end of file
diff --git a/tlapbot/owncast_helpers.py b/tlapbot/owncast_helpers.py
index 8826571..f766627 100644
--- a/tlapbot/owncast_helpers.py
+++ b/tlapbot/owncast_helpers.py
@@ -5,6 +5,7 @@ import click
from flask.cli import with_appcontext
from tlapbot.db import get_db
+
# # # requests stuff # # #
def is_stream_live():
url = current_app.config['OWNCAST_INSTANCE_URL'] + '/api/status'
@@ -12,6 +13,7 @@ def is_stream_live():
print(r.json()["online"])
return r.json()["online"]
+
def give_points_to_chat(db):
url = current_app.config['OWNCAST_INSTANCE_URL'] + '/api/integrations/clients'
headers = {"Authorization": "Bearer " + current_app.config['OWNCAST_ACCESS_TOKEN']}
@@ -22,6 +24,7 @@ def give_points_to_chat(db):
user_id,
current_app.config['POINTS_AMOUNT_GIVEN'])
+
def send_chat(message):
url = current_app.config['OWNCAST_INSTANCE_URL'] + '/api/integrations/chat/send'
headers = {"Authorization": "Bearer " + current_app.config['OWNCAST_ACCESS_TOKEN']}
@@ -31,6 +34,7 @@ def send_chat(message):
# # # db stuff # # #
def read_users_points(db, user_id):
+ """Errors out if user doesn't exist."""
try:
cursor = db.execute(
"SELECT points FROM points WHERE id = ?",
@@ -41,22 +45,36 @@ def read_users_points(db, user_id):
print("Error occured reading points:", e.args[0])
print("To user:", user_id)
+
+def read_all_users_with_username(db, username):
+ try:
+ cursor = db.execute(
+ "SELECT name, points FROM points WHERE name = ?",
+ (username,)
+ )
+ users = cursor.fetchall()
+ return users
+ except Error as e:
+ print("Error occured reading points from username:", e.args[0])
+ print("To user:", username)
+
def give_points_to_user(db, user_id, points):
try:
db.execute(
- "UPDATE points SET points = points + ? WHERE id = ?",
- (points, user_id,)
+ "UPDATE points SET points = points + ? WHERE id = ?",
+ (points, user_id,)
)
db.commit()
except Error as e:
print("Error occured giving points:", e.args[0])
print("To user:", user_id, " amount of points:", points)
+
def use_points(db, user_id, points):
try:
db.execute(
- "UPDATE points SET points = points - ? WHERE id = ?",
- (points, user_id,)
+ "UPDATE points SET points = points - ? WHERE id = ?",
+ (points, user_id,)
)
db.commit()
return True
@@ -65,73 +83,110 @@ def use_points(db, user_id, points):
print("From user:", user_id, " amount of points:", points)
return False
+
def user_exists(db, user_id):
try:
cursor = db.execute(
"SELECT points FROM points WHERE id = ?",
(user_id,)
)
- if cursor.fetchone() == None:
+ if cursor.fetchone() is None:
return False
return True
except Error as e:
print("Error occured checking if user exists:", e.args[0])
print("To user:", user_id)
-""" Adds a new user to the database. Does nothing if user is already in."""
+
def add_user_to_database(db, user_id, display_name):
+ """ Adds a new user to the database. Does nothing if user is already in."""
try:
cursor = db.execute(
- "SELECT points FROM points WHERE id = ?",
+ "SELECT points, name FROM points WHERE id = ?",
(user_id,)
)
- if cursor.fetchone() == None:
+ user = cursor.fetchone()
+ if user is None:
cursor.execute(
"INSERT INTO points(id, name, points) VALUES(?, ?, 10)",
(user_id, display_name)
)
+ if user is not None and user[1] == None:
+ cursor.execute(
+ """UPDATE points
+ SET name = ?
+ WHERE id = ?""",
+ (display_name, user_id)
+ )
db.commit()
except Error as e:
print("Error occured adding user to db:", e.args[0])
print("To user:", user_id, display_name)
+
def change_display_name(db, user_id, new_name):
try:
cursor = db.execute(
- "UPDATE points SET name = ? WHERE id = ?",
- (new_name, user_id)
- )
+ "UPDATE points SET name = ? WHERE id = ?",
+ (new_name, user_id)
+ )
db.commit()
except Error as e:
print("Error occured changing display name:", e.args[0])
print("To user:", user_id, new_name)
-
-def add_to_redeem_queue(db, user_id, redeem_name):
+def add_to_counter(db, counter_name):
try:
cursor = db.execute(
- "INSERT INTO redeem_queue(redeem, redeemer_id) VALUES(?, ?)",
- (redeem_name, user_id)
+ "UPDATE counters SET count = count + 1 WHERE name = ?",
+ (counter_name,)
+ )
+ db.commit()
+ except Error as e:
+ print("Error occured adding to counter:", e.args[0])
+ print("To counter:", counter_name)
+
+
+def add_to_redeem_queue(db, user_id, redeem_name, note=None):
+ try:
+ cursor = db.execute(
+ "INSERT INTO redeem_queue(redeem, redeemer_id, note) VALUES(?, ?, ?)",
+ (redeem_name, user_id, note)
)
db.commit()
except Error as e:
print("Error occured adding to redeem queue:", e.args[0])
- print("To user:", user_id, " with redeem:", redeem_name)
+ print("To user:", user_id, " with redeem:", redeem_name, "with note:", note)
+
def clear_redeem_queue(db):
try:
cursor = db.execute(
- "DELETE FROM redeem_queue"
- )
+ "DELETE FROM redeem_queue"
+ )
+ cursor.execute(
+ """UPDATE counters SET count = 0"""
+ )
db.commit()
except Error as e:
print("Error occured deleting redeem queue:", e.args[0])
+
+def all_counters(db):
+ try:
+ cursor = db.execute(
+ """SELECT counters.name, counters.count FROM counters"""
+ )
+ return cursor.fetchall()
+ except Error as e:
+ print("Error occured selecting all counters:", e.args[0])
+
+
def pretty_redeem_queue(db):
try:
cursor = db.execute(
- """SELECT redeem_queue.created, redeem_queue.redeem, points.name
+ """SELECT redeem_queue.created, redeem_queue.redeem, redeem_queue.note, points.name
FROM redeem_queue
INNER JOIN points
on redeem_queue.redeemer_id = points.id"""
@@ -140,15 +195,30 @@ def pretty_redeem_queue(db):
except Error as e:
print("Error occured selecting pretty redeem queue:", e.args[0])
+
def whole_redeem_queue(db):
try:
cursor = db.execute(
- "SELECT * from redeem_queue"
- )
+ "SELECT * from redeem_queue"
+ )
return cursor.fetchall()
except Error as e:
print("Error occured selecting redeem queue:", e.args[0])
+
+def remove_duplicate_usernames(db, user_id, username):
+ try:
+ cursor = db.execute(
+ """UPDATE points
+ SET name = NULL
+ WHERE name = ? AND NOT id = ?""",
+ (username, user_id)
+ )
+ db.commit()
+ except Error as e:
+ print("Error occured removing duplicate usernames:", e.args[0])
+
+
@click.command('clear-queue')
@with_appcontext
def clear_queue_command():
diff --git a/tlapbot/owncast_redeem_dashboard.py b/tlapbot/owncast_redeem_dashboard.py
index 27c7c64..263f301 100644
--- a/tlapbot/owncast_redeem_dashboard.py
+++ b/tlapbot/owncast_redeem_dashboard.py
@@ -1,20 +1,26 @@
-from flask import render_template, Blueprint
+from flask import render_template, Blueprint, request
from tlapbot.db import get_db
-from tlapbot.owncast_helpers import pretty_redeem_queue
+from tlapbot.owncast_helpers import (pretty_redeem_queue, all_counters,
+ read_all_users_with_username)
from datetime import datetime, timezone
bp = Blueprint('redeem_dashboard', __name__)
-@bp.route('/dashboard',methods=['GET'])
+
+@bp.route('/dashboard', methods=['GET'])
def dashboard():
- queue = pretty_redeem_queue(get_db())
- number_of_drinks = 0
+ db = get_db()
+ queue = pretty_redeem_queue(db)
+ counters = all_counters(db)
+ username = request.args.get("username")
+ if username is not None:
+ users = read_all_users_with_username(db, username)
+ else:
+ users = []
utc_timezone = timezone.utc
- if queue is not None:
- for row in queue:
- if row[1] == "drink":
- number_of_drinks += 1
return render_template('dashboard.html',
queue=queue,
- number_of_drinks=number_of_drinks,
- utc_timezone=utc_timezone)
\ No newline at end of file
+ counters=counters,
+ username=username,
+ users=users,
+ utc_timezone=utc_timezone)
diff --git a/tlapbot/owncast_webhooks.py b/tlapbot/owncast_webhooks.py
index b53ae26..64f8cd2 100644
--- a/tlapbot/owncast_webhooks.py
+++ b/tlapbot/owncast_webhooks.py
@@ -1,11 +1,14 @@
-from flask import Flask,request,json,Blueprint
+from flask import Flask, request, json, Blueprint, current_app
from sqlite3 import Error
from tlapbot.db import get_db
-from tlapbot.owncast_helpers import *
+from tlapbot.owncast_helpers import (add_user_to_database, change_display_name,
+ user_exists, send_chat, read_users_points, remove_duplicate_usernames)
+from tlapbot.redeems_handler import handle_redeem
bp = Blueprint('owncast_webhooks', __name__)
-@bp.route('/owncastWebhook',methods=['POST'])
+
+@bp.route('/owncastWebhook', methods=['POST'])
def owncast_webhook():
data = request.json
db = get_db()
@@ -14,10 +17,14 @@ def owncast_webhook():
display_name = data["eventData"]["user"]["displayName"]
# CONSIDER: join points for joining stream
add_user_to_database(db, user_id, display_name)
+ if data["eventData"]["user"]["authenticated"]:
+ remove_duplicate_usernames(db, user_id, display_name)
elif data["type"] == "NAME_CHANGE":
user_id = data["eventData"]["user"]["id"]
new_name = data["eventData"]["newName"]
change_display_name(db, user_id, new_name)
+ if data["eventData"]["user"]["authenticated"]:
+ remove_duplicate_usernames(db, user_id, new_name)
elif data["type"] == "CHAT":
user_id = data["eventData"]["user"]["id"]
display_name = data["eventData"]["user"]["displayName"]
@@ -25,28 +32,27 @@ def owncast_webhook():
print(f'{data["eventData"]["body"]}')
if "!help" in data["eventData"]["body"]:
message = """Tlapbot commands:
+ !help to see this help message.
!points to see your points.
- !drink to redeem a pitíčko for 60 points.
- That's it for now."""
+ !name_update to force name update if tlapbot didn't catch it.
+ Tlapbot redeems:\n"""
+ for redeem, redeem_info in current_app.config['REDEEMS'].items():
+ message += (f"!{redeem} for {redeem_info['price']} points.\n")
+ # TODO: also make this customizable
send_chat(message)
elif "!points" in data["eventData"]["body"]:
if not user_exists(db, user_id):
add_user_to_database(db, user_id, display_name)
points = read_users_points(db, user_id)
- message = "{}'s points: {}".format(display_name, points)
+ message = f"{display_name}'s points: {points}"
send_chat(message)
- elif "!drink" in data["eventData"]["body"]:
- points = read_users_points(db, user_id)
- if points is not None and points >= 60:
- if use_points(db, user_id, 60):
- add_to_redeem_queue(db, user_id, "drink")
- send_chat("Pitíčko redeemed for 60 points.")
- else:
- send_chat("Pitíčko not redeemed because of an error.")
- else:
- send_chat("Can't redeem pitíčko, you don't have enough points.")
elif "!name_update" in data["eventData"]["body"]:
# Forces name update in case bot didn't catch the NAME_CHANGE
- # event. Theoretically only needed when bot was off.
+ # event. Also removes saved usernames from users with same name
+ # if user is authenticated.
change_display_name(db, user_id, display_name)
- return data
\ No newline at end of file
+ if data["eventData"]["user"]["authenticated"]:
+ remove_duplicate_usernames(db, user_id, display_name)
+ elif data["eventData"]["body"].startswith("!"): # TODO: make prefix configurable
+ handle_redeem(data["eventData"]["body"], user_id)
+ return data
diff --git a/tlapbot/redeems_handler.py b/tlapbot/redeems_handler.py
new file mode 100644
index 0000000..2fa717c
--- /dev/null
+++ b/tlapbot/redeems_handler.py
@@ -0,0 +1,41 @@
+from flask import current_app
+from tlapbot.db import get_db
+from tlapbot.owncast_helpers import (use_points, add_to_redeem_queue,
+ add_to_counter, read_users_points, send_chat)
+
+
+def handle_redeem(message, user_id):
+ split_message = message[1:].split(maxsplit=1)
+ redeem = split_message[0]
+ if len(split_message) == 1:
+ note = None
+ else:
+ note = split_message[1]
+
+ if redeem in current_app.config['REDEEMS']:
+ db = get_db()
+ price = current_app.config['REDEEMS'][redeem]["price"]
+ redeem_type = current_app.config['REDEEMS'][redeem]["type"]
+ points = read_users_points(db, user_id)
+ if points is not None and points >= price:
+ if redeem_type == "counter":
+ add_to_counter(db, redeem)
+ use_points(db, user_id, price)
+ send_chat(f"{redeem} redeemed for {price} points.")
+ elif redeem_type == "list":
+ add_to_redeem_queue(db, user_id, redeem)
+ use_points(db, user_id, price)
+ send_chat(f"{redeem} redeemed for {price} points.")
+ elif redeem_type == "note":
+ if note is not None:
+ add_to_redeem_queue(db, user_id, redeem, note)
+ use_points(db, user_id, price)
+ send_chat(f"{redeem} redeemed for {price} points.")
+ else:
+ send_chat(f"Cannot redeem {redeem}, no note included.")
+ else:
+ send_chat(f"{redeem} not redeemed because of an error.")
+ else:
+ send_chat(f"Can't redeem {redeem}, you don't have enough points.")
+ else:
+ send_chat("Can't redeem, redeem not found.")
diff --git a/tlapbot/schema.sql b/tlapbot/schema.sql
index 0770ea7..681cc70 100644
--- a/tlapbot/schema.sql
+++ b/tlapbot/schema.sql
@@ -1,16 +1,23 @@
-DROP TABLE IF EXISTS points;
+DROP TABLE IF EXISTS counters;
DROP TABLE IF EXISTS redeem_queue;
-CREATE TABLE points (
+CREATE TABLE IF NOT EXISTS points (
id TEXT PRIMARY KEY,
name TEXT,
points INTEGER
);
+CREATE TABLE counters (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ name TEXT NOT NULL,
+ count INTEGER NOT NULL
+);
+
CREATE TABLE redeem_queue (
id INTEGER PRIMARY KEY AUTOINCREMENT,
created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
- redeem TEXT,
- redeemer_id TEXT,
+ redeem TEXT NOT NULL,
+ redeemer_id TEXT NOT NULL,
+ note TEXT,
FOREIGN KEY (redeemer_id) REFERENCES points (id)
);
\ No newline at end of file
diff --git a/tlapbot/templates/dashboard.html b/tlapbot/templates/dashboard.html
index f4865e2..bebbc1d 100644
--- a/tlapbot/templates/dashboard.html
+++ b/tlapbot/templates/dashboard.html
@@ -5,12 +5,38 @@
Redeems Dashboard
+ {% if (username and users ) %}
+
+
+ Points balance: |
+
+
+ {% for user in users %}
- Number of drinks: |
- {{ number_of_drinks }} |
+ {{ user[0] }} |
+ {{ user[1] }} |
+ {% endfor %}
+ {% endif %}
+
+ {% if counters %}
+
+
+
+ Counters |
+
+
+ {% for counter in counters %}
+
+ {{ counter[0] }} |
+ {{ counter[1] }} |
+
+ {% endfor %}
+
+ {% endif %}
+
{% if queue %}
@@ -18,13 +44,17 @@
time |
redeem |
redeemer |
+ note |
{% for row in queue %}
{{ row[0].replace(tzinfo=utc_timezone).astimezone().strftime("%H:%M") }} |
{{ row[1] }} |
+ {{ row[3] }} |
+ {% if row[2] %}
{{ row[2] }} |
+ {% endif %}
{% endfor %}