12 Commits
Author SHA1 Message Date
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
9 changed files with 130 additions and 60 deletions
+18 -4
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,17 +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": {"type": "milestone", "info": "Streamer will go nap when the goal is reached.", "goal": 1000},
"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 entries for `"type"`,
an entry for `"price"` unless the redeem is a milestone,
and optionally `"info"` and `"category"`.
If the `"type"` is `"milestone"`, there's an additional required `"goal"` field as well.
- `"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.
- `"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. - `"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). - `"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).
- `"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.
- `"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`,
+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=[
+2
View File
@@ -49,6 +49,8 @@ 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():
+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": {"type": "milestone", "info": "Streamer will go nap when the goal is reached.", "goal": 1000},
"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"]}
} }
+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()
-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.")
+4
View File
@@ -139,7 +139,11 @@
{% for redeem, redeem_info in redeems.items() %} {% for redeem, redeem_info in redeems.items() %}
<tbody> <tbody>
<td>{{ prefix }}{{ redeem }}</td> <td>{{ prefix }}{{ redeem }}</td>
{% if redeem_info["type"] == "milestone" %}
<td></td>
{% else %}
<td>{{ redeem_info["price"] }}</td> <td>{{ redeem_info["price"] }}</td>
{% endif %}
<td>{{ redeem_info["type"] }}</td> <td>{{ redeem_info["type"] }}</td>
{% if redeem_info["info"] %} {% if redeem_info["info"] %}
<td>{{ redeem_info["info"] }}</td> <td>{{ redeem_info["info"] }}</td>