17 Commits
Author SHA1 Message Date
lili acbc93bedd auto-refresh base
untested
2024-02-04 11:31:42 +01:00
lili e5e88bf520 Merge branch 'master' of github.com:SleepyLili/tlapbot into develop 2024-02-04 11:28:51 +01:00
lili b4a6c4e41c html/jinja formatting improvements
thanks chat
2024-02-03 18:47:42 +01:00
lili 8c19088640 readme: reword milestone "goal" and "price"
should hopefully suggest more clearly that milestones need goal
and everything else needs price
also changed the example to list goal first
2023-07-13 12:44:15 +02:00
lili 5316f7e205 improve default_redeems
list milestone "goal" first in the redeems dictionary
2023-07-13 12:42:46 +02:00
lili 31426fd851 promote to 1.2.1 2023-07-10 11:16:44 +02:00
lili 8d5a3cda42 update readme for 1.2.1 2023-07-10 11:13:31 +02:00
lili e237e7f885 declare alpha version 1.2.1a1 2023-07-03 12:56:03 +02:00
lili 80037593a9 fix redeem handler for redeems with no price
milestone needs to be handled first
2023-07-03 12:27:45 +02:00
lili d484a0a363 fix reset_milestone 2023-07-03 12:18:29 +02:00
lili 348e674f74 make price tab in dashboard blank for milestones 2023-07-03 11:02:09 +02:00
lili ca9aa6d79d add reset and hard-reset command to init 2023-07-03 10:46:35 +02:00
lili ae42a052de add true/false fail checks to click commands
previously click would print stuff like "X done succesfully" after error
now it'll be more clear that a failure has occured
2023-06-26 13:40:26 +02:00
lili d1416d3706 add reset-milestone and hard-reset-milestone
commands to click
2023-06-26 13:28:34 +02:00
lili f66b125ac3 remove unused start_milestone function 2023-06-26 13:12:28 +02:00
lili 6f6fc10d0f remove minimum bid for #15 2023-06-20 14:39:19 +02:00
lili bf68f518da add error handling and response check to requests 2023-06-20 13:41:22 +02:00
12 changed files with 233 additions and 92 deletions
+18 -5
View File
@@ -265,6 +265,16 @@ python -m flask refresh-milestones
``` ```
Running this command shouldn't reset progress on milestones that are already in the database Running this command shouldn't reset progress on milestones that are already in the database
and are still in the redeems file. and are still in the redeems file.
#### reset-milestone
Resets progress on a milestone, but only if the milestone had been completed.
```bash
python -m flask reset-milestone milestone
```
#### hard-reset-milestone
Resets progress on a milestone, regardless of completion status.
```bash
python -m flask hard-reset-milestone milestone
```
## Configuration files ## Configuration files
Configuration files should be in the instance folder. For folder installation of tlapbot, Configuration files should be in the instance folder. For folder installation of tlapbot,
that's `instance/` from the root of the Github repository. that's `instance/` from the root of the Github repository.
@@ -313,18 +323,21 @@ REDEEMS={
"lurk": {"price": 1, "type": "counter", "info": "Let us know you're going to lurk."}, "lurk": {"price": 1, "type": "counter", "info": "Let us know you're going to lurk."},
"react": {"price": 200, "type": "note", "info": "Attach link to a video for me to react to."}, "react": {"price": 200, "type": "note", "info": "Attach link to a video for me to react to."},
"request": {"price": 100, "type": "note", "info": "Request a level, gamemode, skin, etc."}, "request": {"price": 100, "type": "note", "info": "Request a level, gamemode, skin, etc."},
"go_nap": {"price": 1, "type": "milestone", "info": "Streamer will go nap when the goal is reached.", "goal": 1000}, "go_nap": {"goal": 1000, "type": "milestone", "info": "Streamer will go nap when the goal is reached."},
"inactive": {"price": 100, "type": "note", "info": "Example redeem that is inactive by default", "category": ["inactive"]} "inactive": {"price": 100, "type": "note", "info": "Example redeem that is inactive by default", "category": ["inactive"]}
} }
``` ```
#### File format #### File format
`redeems.py` is a config file with just a `REDEEMS` key, that assigns a dictionary of redeems to it. `redeems.py` is a config file with just a `REDEEMS` key, that assigns a dictionary of redeems to it.
Each dictionary entry is a redeem, and the dictionary keys are strings that decides the chat command for the redeem. The value is another dictionary that needs to have entries for `"price"`, `"type"` and optionally `"info"` and `"category"`. If the `"type"` is `"milestone"`, there's an additional required `"goal"` field as well. Each dictionary entry is a redeem, and the dictionary keys are strings that decides the chat command for the redeem.
The value is another dictionary that needs to have an entry for `"type"` and
an entry for `"price"` for non-milestones or `"goal"` for milestones.
Optionally, each redeem can also have `"info"` and `"category"` entries.
- `"price"` value should be an integer that decides how many points the redeem will cost. For milestone redeems, `"price"` determines minimum bid. - `"price"` value should be an integer that decides how many points the redeem will cost. Milestone redeems don't use the `"price"` value, they instead need to have a `"goal"`.
- `"type"` value should be either `"list"`, `"counter"`, `"note"` or `"milestone"`. This decided the redeem's type, and whether it will show up as a counter at the top of the dashboard or as an entry in the "recent redeems" chart.
- `"info"` value should be a string that describes what the command does. It's optional, but I recommend writing one for all `"list"` and `"note"` redeems (so that chatters know that they should write a note).
- `"goal"` is a required field for milestone goals. It should be an integer, deciding the amount of points required to complete the milestone. - `"goal"` is a required field for milestone goals. It should be an integer, deciding the amount of points required to complete the milestone.
- `"type"` value should be either `"list"`, `"counter"`, `"note"` or `"milestone"`. This decided the redeem's type, and whether it will show up as a counter at the top of the dashboard or as an entry in the "recent redeems" chart.
- `"info"` value should be a string that describes what the command does. It's optional, but I recommend writing one for all `"list"`, `"note"` and `"milestone"` redeems (so that chatters know what they're redeeming and whether they should leave a note).
- `"category"` is an optional list of strings, the categories the redeem is in. - `"category"` is an optional list of strings, the categories the redeem is in.
If a category from the list is in `ACTIVE_CATEGORIES` from `config.py`, If a category from the list is in `ACTIVE_CATEGORIES` from `config.py`,
then the redeem will be active. It will not be active if none of the categories then the redeem will be active. It will not be active if none of the categories
+1 -1
View File
@@ -2,7 +2,7 @@ from setuptools import find_packages, setup
setup( setup(
name='tlapbot', name='tlapbot',
version='1.2.0', version='1.2.1',
packages=find_packages(), packages=find_packages(),
include_package_data=True, include_package_data=True,
install_requires=[ install_requires=[
+10
View File
@@ -2,9 +2,12 @@ import os
import logging import logging
from flask import Flask from flask import Flask
from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.schedulers.background import BackgroundScheduler
from datetime import datetime
from tlapbot.db import get_db from tlapbot.db import get_db
from tlapbot.owncast_requests import is_stream_live, give_points_to_chat from tlapbot.owncast_requests import is_stream_live, give_points_to_chat
from tlapbot.redeems import remove_inactive_redeems from tlapbot.redeems import remove_inactive_redeems
from tlapbot.helpers import (get_last_online_time, delete_last_online_time,
save_last_online_time)
def create_app(test_config=None): def create_app(test_config=None):
app = Flask(__name__, instance_relative_config=True) app = Flask(__name__, instance_relative_config=True)
@@ -49,14 +52,21 @@ def create_app(test_config=None):
app.cli.add_command(db.refresh_counters_command) app.cli.add_command(db.refresh_counters_command)
app.cli.add_command(db.refresh_and_clear_command) app.cli.add_command(db.refresh_and_clear_command)
app.cli.add_command(db.refresh_milestones_command) app.cli.add_command(db.refresh_milestones_command)
app.cli.add_command(db.reset_milestone_command)
app.cli.add_command(db.hard_reset_milestone_command)
# scheduler job for giving points to users # scheduler job for giving points to users
def proxy_job(): def proxy_job():
with app.app_context(): with app.app_context():
if is_stream_live(): if is_stream_live():
if get_last_online_time:
delete_last_online_time()
app.logger.info("Stream is LIVE. Giving points to chat.") app.logger.info("Stream is LIVE. Giving points to chat.")
give_points_to_chat(get_db()) give_points_to_chat(get_db())
else: else:
if not get_last_online_time:
# TODO: error state
save_last_online_time(get_db(), datetime.now(), False)
app.logger.info("Stream is NOT LIVE. (Not giving points to chat.)") app.logger.info("Stream is NOT LIVE. (Not giving points to chat.)")
# start scheduler that will give points to users # start scheduler that will give points to users
+63 -13
View File
@@ -4,6 +4,7 @@ import click
from flask import current_app, g from flask import current_app, g
from flask.cli import with_appcontext from flask.cli import with_appcontext
from tlapbot.redeems import milestone_complete
def get_db(): def get_db():
if 'db' not in g: if 'db' not in g:
@@ -34,6 +35,8 @@ def insert_counters(db):
db.commit() db.commit()
except sqlite3.Error as e: except sqlite3.Error as e:
print("Failed inserting counters to db:", e.args[0]) print("Failed inserting counters to db:", e.args[0])
return False
return True
def init_db(): def init_db():
@@ -42,7 +45,8 @@ def init_db():
with current_app.open_resource('schema.sql') as f: with current_app.open_resource('schema.sql') as f:
db.executescript(f.read().decode('utf8')) db.executescript(f.read().decode('utf8'))
insert_counters(db) if insert_counters(db):
return True
def clear_redeem_queue(): def clear_redeem_queue():
@@ -58,6 +62,8 @@ def clear_redeem_queue():
db.commit() db.commit()
except sqlite3.Error as e: except sqlite3.Error as e:
print("Error occured deleting redeem queue:", e.args[0]) print("Error occured deleting redeem queue:", e.args[0])
return False
return True
def refresh_counters(): def refresh_counters():
@@ -68,7 +74,9 @@ def refresh_counters():
db.commit() db.commit()
except sqlite3.Error as e: except sqlite3.Error as e:
print("Error occured deleting old counters:", e.args[0]) print("Error occured deleting old counters:", e.args[0])
insert_counters(db) return False
if insert_counters(db):
return True
def refresh_milestones(): def refresh_milestones():
@@ -89,6 +97,7 @@ def refresh_milestones():
db.commit() db.commit()
except sqlite3.Error as e: except sqlite3.Error as e:
print("Failed deleting old milestones from db:", e.args[0]) print("Failed deleting old milestones from db:", e.args[0])
return False
# add new milestones # add new milestones
try: try:
@@ -113,24 +122,46 @@ def refresh_milestones():
db.commit() db.commit()
except sqlite3.Error as e: except sqlite3.Error as e:
print("Failed inserting milestones to db:", e.args[0]) print("Failed inserting milestones to db:", e.args[0])
return False
return True
def reset_milestone(milestone):
if not milestone in current_app.config['REDEEMS']:
print(f"Failed resetting milestone, {milestone} not in redeems file.")
return False
try:
db = get_db()
db.execute(
"DELETE FROM milestones WHERE name = ?",
(milestone,)
)
db.execute(
"INSERT INTO milestones(name, progress, goal, complete) VALUES(?, ?, ?, FALSE)",
(milestone, 0, current_app.config['REDEEMS'][milestone]['goal'])
)
db.commit()
return True
except sqlite3.Error as e:
current_app.logger.error(f"Error occured adding a milestone: {e.args[0]}")
return False
@click.command('init-db') @click.command('init-db')
@with_appcontext @with_appcontext
def init_db_command(): def init_db_command():
"""Clear the existing data and create new tables.""" """Clear the existing data and create new tables."""
init_db() if init_db():
click.echo('Initialized the database.') click.echo('Initialized the database.')
@click.command('clear-queue') @click.command('clear-queue')
@with_appcontext @with_appcontext
def clear_queue_command(): def clear_queue_command():
"""Remove all redeems from the redeem queue.""" """Remove all redeems from the redeem queue."""
clear_redeem_queue() if clear_redeem_queue():
click.echo('Cleared redeem queue.') click.echo('Cleared redeem queue.')
@click.command('refresh-counters') @click.command('refresh-counters')
@@ -138,17 +169,16 @@ def clear_queue_command():
def refresh_counters_command(): def refresh_counters_command():
"""Refresh counters from current config file. """Refresh counters from current config file.
(Remove old ones, add new ones.)""" (Remove old ones, add new ones.)"""
refresh_counters() if refresh_counters():
click.echo('Counters refreshed.') click.echo('Counters refreshed.')
@click.command('clear-refresh') @click.command('clear-refresh')
@with_appcontext @with_appcontext
def refresh_and_clear_command(): def refresh_and_clear_command():
"""Refresh counters and clear queue.""" """Refresh counters and clear queue."""
refresh_counters() if refresh_counters() and clear_redeem_queue():
clear_redeem_queue() click.echo('Counters refreshed and queue cleared.')
click.echo('Counters refreshed and queue cleared.')
@click.command('refresh-milestones') @click.command('refresh-milestones')
@@ -156,8 +186,28 @@ def refresh_and_clear_command():
def refresh_milestones_command(): def refresh_milestones_command():
"""Initialize all milestones from the redeems file, """Initialize all milestones from the redeems file,
delete milestones not in redeem file.""" delete milestones not in redeem file."""
refresh_milestones() if refresh_milestones():
click.echo('Refreshed milestones.') click.echo('Refreshed milestones.')
@click.command('reset-milestone')
@click.argument('milestone')
def reset_milestone_command(milestone):
"""Resets a completed milestone back to zero."""
if milestone_complete(get_db(), milestone):
if reset_milestone(milestone):
click.echo(f"Reset milestone {milestone}.")
else:
click.echo(f"Could not reset milestone {milestone}, milestone not completed.")
click.echo("(You can hard-reset-milestone if you really want to reset it.)")
@click.command('hard-reset-milestone')
@click.argument('milestone')
def hard_reset_milestone_command(milestone):
"""Resets any milestone back to zero."""
if reset_milestone(milestone):
click.echo(f"Hard reset milestone {milestone}.")
def init_app(app): def init_app(app):
+1 -1
View File
@@ -3,6 +3,6 @@ REDEEMS={
"lurk": {"price": 1, "type": "counter", "info": "Let us know you're going to lurk."}, "lurk": {"price": 1, "type": "counter", "info": "Let us know you're going to lurk."},
"react": {"price": 200, "type": "note", "info": "Attach link to a video for me to react to."}, "react": {"price": 200, "type": "note", "info": "Attach link to a video for me to react to."},
"request": {"price": 100, "type": "note", "info": "Request a level, gamemode, skin, etc."}, "request": {"price": 100, "type": "note", "info": "Request a level, gamemode, skin, etc."},
"go_nap": {"price": 1, "type": "milestone", "info": "Streamer will go nap when the goal is reached.", "goal": 1000}, "go_nap": {"goal": 1000, "type": "milestone", "info": "Streamer will go nap when the goal is reached."},
"inactive": {"price": 100, "type": "note", "info": "Example redeem that is inactive by default", "category": ["inactive"]} "inactive": {"price": 100, "type": "note", "info": "Example redeem that is inactive by default", "category": ["inactive"]}
} }
+31
View File
@@ -95,6 +95,37 @@ def add_user_to_database(db, user_id, display_name):
current_app.logger.error(f"To user id: {user_id}, with display name: {display_name}") current_app.logger.error(f"To user id: {user_id}, with display name: {display_name}")
def save_last_online_time(db, timestamp, from_owncast):
try:
db.execute(
"INSERT OVERWRITE last_online_time(id, last_online_time, from_owncast)",
(1, timestamp, from_owncast)
)
db.commit()
except Error as e:
current_app.logger.error(f"Error occured saving last online time: {e.args[0]}")
current_app.logger.error(f"Timestamp: {timestamp}, from_owncast: {from_owncast}")
def get_last_online_time(db):
try:
cursor = db.execute(
"SELECT last_online_time FROM last_online_time WHERE id = 1"
)
last_online_time = cursor.fetchone()
return last_online_time
except Error as e:
current_app.logger.error(f"Error occured reading last online time: {e.args[0]}")
def delete_last_online_time(db):
try:
db.execute("DELETE FROM last_online_time")
db.commit()
except Error as e:
current_app.logger.error(f"Error occured deleting last online time: {e.args[0]}")
def change_display_name(db, user_id, new_name): def change_display_name(db, user_id, new_name):
try: try:
cursor = db.execute( cursor = db.execute(
+21 -5
View File
@@ -16,17 +16,33 @@ def is_stream_live():
def give_points_to_chat(db): def give_points_to_chat(db):
url = current_app.config['OWNCAST_INSTANCE_URL'] + '/api/integrations/clients' url = current_app.config['OWNCAST_INSTANCE_URL'] + '/api/integrations/clients'
headers = {"Authorization": "Bearer " + current_app.config['OWNCAST_ACCESS_TOKEN']} headers = {"Authorization": "Bearer " + current_app.config['OWNCAST_ACCESS_TOKEN']}
r = requests.get(url, headers=headers) try:
r = requests.get(url, headers=headers)
except requests.exceptions.RequestException as e:
current_app.logger.error(f"Error occured getting users to give points to: {e.args[0]}")
return
if r.status_code != 200:
current_app.logger.error(f"Error occured when giving points: Response code not 200.")
current_app.logger.error(f"Response code received: {r.status_code}.")
current_app.logger.error(f"Check owncast instance url and access key.")
return
unique_users = set(map(lambda user_object: user_object["user"]["id"], r.json())) unique_users = set(map(lambda user_object: user_object["user"]["id"], r.json()))
for user_id in unique_users: for user_id in unique_users:
give_points_to_user(db, give_points_to_user(db, user_id, current_app.config['POINTS_AMOUNT_GIVEN'])
user_id,
current_app.config['POINTS_AMOUNT_GIVEN'])
def send_chat(message): def send_chat(message):
url = current_app.config['OWNCAST_INSTANCE_URL'] + '/api/integrations/chat/send' url = current_app.config['OWNCAST_INSTANCE_URL'] + '/api/integrations/chat/send'
headers = {"Authorization": "Bearer " + current_app.config['OWNCAST_ACCESS_TOKEN']} headers = {"Authorization": "Bearer " + current_app.config['OWNCAST_ACCESS_TOKEN']}
r = requests.post(url, headers=headers, json={"body": message}) try:
r = requests.post(url, headers=headers, json={"body": message})
except requests.exceptions.RequestException as e:
current_app.logger.error(f"Error occured sending chat message: {e.args[0]}")
return
if r.status_code != 200:
current_app.logger.error(f"Error occured when sending chat: Response code not 200.")
current_app.logger.error(f"Response code received: {r.status_code}.")
current_app.logger.error(f"Check owncast instance url and access key.")
return
return r.json() return r.json()
+20 -2
View File
@@ -1,11 +1,13 @@
from flask import Flask, request, json, Blueprint, current_app from flask import Flask, request, json, Blueprint, current_app
from tlapbot.db import get_db from datetime import datetime
from tlapbot.db import get_db, refresh_counters, clear_redeem_queue
from tlapbot.owncast_requests import send_chat from tlapbot.owncast_requests import send_chat
from tlapbot.owncast_helpers import (add_user_to_database, change_display_name, from tlapbot.owncast_helpers import (add_user_to_database, change_display_name,
read_users_points, remove_duplicate_usernames) read_users_points, remove_duplicate_usernames, get_last_online_time, delete_last_online_time)
from tlapbot.help_message import send_help from tlapbot.help_message import send_help
from tlapbot.redeems_handler import handle_redeem from tlapbot.redeems_handler import handle_redeem
# might need datetime timestamp
bp = Blueprint('owncast_webhooks', __name__) bp = Blueprint('owncast_webhooks', __name__)
@@ -15,6 +17,22 @@ def owncast_webhook():
data = request.json data = request.json
db = get_db() db = get_db()
if data["type"] == "STREAM_STARTED":
# TODO: make this a function, import here and in init
delete_last_online_time(db)
last_online = get_last_online_time(db)
if last_online and current_app.config['AUTO_REFRESH']:
time_difference = datetime.now() - last_online
if time_difference.seconds//60 > current_app.config['RECONNECT_TIME']:
if refresh_counters() and clear_redeem_queue():
current_app.logger.debug(f'Counters refreshed, redeem queue cleared.')
else:
current_app.logger.error(
f'Error occured when automatically clearing queue and resetting counters.'
)
elif data["type"] == "STREAM_STOPPED":
save_last_online_time(db, datetime.now(), True)
# Make sure user is in db before doing anything else. # Make sure user is in db before doing anything else.
if data["type"] in ["CHAT", "NAME_CHANGED", "USER_JOINED"]: if data["type"] in ["CHAT", "NAME_CHANGED", "USER_JOINED"]:
user_id = data["eventData"]["user"]["id"] user_id = data["eventData"]["user"]["id"]
-18
View File
@@ -49,24 +49,6 @@ def add_to_redeem_queue(db, user_id, redeem_name, note=None):
return False return False
def start_milestone(db, redeem_name):
try:
cursor = db.execute(
"SELECT progress, goal FROM milestones WHERE name = ?",
(redeem_name,)
)
milestone = cursor.fetchone()
current_app.logger.error(f"Milestone: {milestone}")
if milestone is None:
cursor = db.execute(
"INSERT INTO milestones(name, progress, goal) VALUES(?, ?, ?)",
(redeem_name, 0, current_app.config['REDEEMS'][redeem_name]['goal'])
)
db.commit()
except Error as e:
current_app.logger.error(f"Error occured adding a milestone: {e.args[0]}")
def add_to_milestone(db, user_id, redeem_name, points_donated): def add_to_milestone(db, user_id, redeem_name, points_donated):
try: try:
cursor = db.execute( cursor = db.execute(
+20 -18
View File
@@ -22,10 +22,29 @@ def handle_redeem(message, user_id):
return return
db = get_db() db = get_db()
price = current_app.config['REDEEMS'][redeem]["price"]
redeem_type = current_app.config['REDEEMS'][redeem]["type"] redeem_type = current_app.config['REDEEMS'][redeem]["type"]
points = read_users_points(db, user_id) points = read_users_points(db, user_id)
# handle milestone first because it doesn't have a price
if redeem_type == "milestone":
if milestone_complete(db, redeem):
send_chat(f"Can't redeem {redeem}, that milestone was already completed!")
elif not note:
send_chat(f"Cannot redeem {redeem}, no amount of points specified.")
elif not note.isdigit():
send_chat(f"Cannot redeem {redeem}, amount of points is not an integer.")
elif int(note) > points:
send_chat(f"Can't redeem {redeem}, you're donating more points than you have.")
elif add_to_milestone(db, user_id, redeem, int(note)):
send_chat(f"Succesfully donated to {redeem} milestone!")
if check_apply_milestone_completion(db, redeem):
send_chat(f"Milestone goal {redeem} complete!")
else:
send_chat(f"Redeeming milestone {redeem} failed.")
return
# handle redeems with price argument
price = current_app.config['REDEEMS'][redeem]["price"]
if not points or points < price: if not points or points < price:
send_chat(f"Can't redeem {redeem}, you don't have enough points.") send_chat(f"Can't redeem {redeem}, you don't have enough points.")
return return
@@ -50,22 +69,5 @@ def handle_redeem(message, user_id):
send_chat(f"{redeem} redeemed for {price} points.") send_chat(f"{redeem} redeemed for {price} points.")
else: else:
send_chat(f"Redeeming {redeem} failed.") send_chat(f"Redeeming {redeem} failed.")
elif redeem_type == "milestone":
if milestone_complete(db, redeem):
send_chat(f"Can't redeem {redeem}, that milestone was already completed!")
elif not note:
send_chat(f"Cannot redeem {redeem}, no amount of points specified.")
elif not note.isdigit():
send_chat(f"Cannot redeem {redeem}, amount of points is not an integer.")
elif int(note) < price:
send_chat(f"Can't redeem {redeem}, your donation is below the minimum bid of {price}.")
elif int(note) > points:
send_chat(f"Can't redeem {redeem}, you're donating more points than you have.")
elif add_to_milestone(db, user_id, redeem, int(note)):
send_chat(f"Succesfully donated to {redeem} milestone!")
if check_apply_milestone_completion(db, redeem):
send_chat(f"Milestone goal {redeem} complete!")
else:
send_chat(f"Redeeming {redeem} failed.")
else: else:
send_chat(f"{redeem} not redeemed, type of redeem not found.") send_chat(f"{redeem} not redeemed, type of redeem not found.")
+5
View File
@@ -29,4 +29,9 @@ CREATE TABLE redeem_queue (
redeemer_id TEXT NOT NULL, redeemer_id TEXT NOT NULL,
note TEXT, note TEXT,
FOREIGN KEY (redeemer_id) REFERENCES points (id) FOREIGN KEY (redeemer_id) REFERENCES points (id)
);
CREATE TABLE online_time(
id INTEGER PRIMARY KEY,
online_time TIMESTAMP NOT NULL
); );
+43 -29
View File
@@ -30,12 +30,14 @@
<th>Your points balance</th> <th>Your points balance</th>
</tr> </tr>
</thead> </thead>
{% for user in users %}
<tbody> <tbody>
<td> {{ user[0] }} </td> {% for user in users %}
<td> {{ user[1] }} </td> <tr>
</tbody> <td> {{ user[0] }} </td>
<td> {{ user[1] }} </td>
</tr>
{% endfor %} {% endfor %}
</tbody>
</table> </table>
{% endif %} {% endif %}
@@ -52,12 +54,14 @@
<th>Active counters</th> <th>Active counters</th>
</tr> </tr>
</thead> </thead>
{% for counter in counters %}
<tbody> <tbody>
<td> {{ counter[0] }} </td> {% for counter in counters %}
<td> {{ counter[1] }} </td> <tr>
</tbody> <td> {{ counter[0] }} </td>
<td> {{ counter[1] }} </td>
</tr>
{% endfor %} {% endfor %}
</tbody>
</table> </table>
{% endif %} {% endif %}
{% if milestones %} {% if milestones %}
@@ -68,13 +72,15 @@
<th>Progress</th> <th>Progress</th>
</tr> </tr>
</thead> </thead>
{% for milestone in milestones %}
<tbody> <tbody>
<td> {{ milestone[0] }} </td> {% for milestone in milestones %}
<td> <progress id="file" max={{ milestone[2] }} value={{ milestone[1] }}></progress></td> <tr>
<td> {{ milestone[1] }} / {{ milestone[2] }}</td> <td> {{ milestone[0] }} </td>
</tbody> <td> <progress id="file" max={{ milestone[2] }} value={{ milestone[1] }}></progress></td>
<td> {{ milestone[1] }} / {{ milestone[2] }}</td>
</tr>
{% endfor %} {% endfor %}
</tbody>
</table> </table>
{% endif %} {% endif %}
{% endif %} {% endif %}
@@ -99,16 +105,18 @@
<th>Note</th> <th>Note</th>
</tr> </tr>
</thead> </thead>
{% for row in queue %}
<tbody> <tbody>
<td>{{ row[0].replace(tzinfo=utc_timezone).astimezone().strftime("%H:%M") }}</td> {% for row in queue %}
<td>{{ row[1] }}</td> <tr>
<td>{{ row[3] }}</td> <td>{{ row[0].replace(tzinfo=utc_timezone).astimezone().strftime("%H:%M") }}</td>
{% if row[2] %} <td>{{ row[1] }}</td>
<td>{{ row[2] }}</td> <td>{{ row[3] }}</td>
{% endif %} {% if row[2] %}
</tbody> <td>{{ row[2] }}</td>
{% endif %}
</tr>
{% endfor %} {% endfor %}
</tbody>
</table> </table>
{% endif %} {% endif %}
</body> </body>
@@ -136,16 +144,22 @@
<th>Description</th> <th>Description</th>
</tr> </tr>
</thead> </thead>
{% for redeem, redeem_info in redeems.items() %}
<tbody> <tbody>
<td>{{ prefix }}{{ redeem }}</td> {% for redeem, redeem_info in redeems.items() %}
<td>{{ redeem_info["price"] }}</td> <tr>
<td>{{ redeem_info["type"] }}</td> <td>{{ prefix }}{{ redeem }}</td>
{% if redeem_info["info"] %} {% if redeem_info["type"] == "milestone" %}
<td>{{ redeem_info["info"] }}</td> <td></td>
{% endif %} {% else %}
</tbody> <td>{{ redeem_info["price"] }}</td>
{% endif %}
<td>{{ redeem_info["type"] }}</td>
{% if redeem_info["info"] %}
<td>{{ redeem_info["info"] }}</td>
{% endif %}
</tr>
{% endfor %} {% endfor %}
</tbody>
</table> </table>
{% endif %} {% endif %}
</body> </body>