Compare commits

..

10 Commits

Author SHA1 Message Date
Lili (Tlapka) 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 (Tlapka) 5316f7e205 improve default_redeems
list milestone "goal" first in the redeems dictionary
2023-07-13 12:42:46 +02:00
Lili (Tlapka) 31426fd851 promote to 1.2.1 2023-07-10 11:16:44 +02:00
Lili (Tlapka) 8d5a3cda42 update readme for 1.2.1 2023-07-10 11:13:31 +02:00
Lili (Tlapka) e237e7f885 declare alpha version 1.2.1a1 2023-07-03 12:56:03 +02:00
Lili (Tlapka) 80037593a9 fix redeem handler for redeems with no price
milestone needs to be handled first
2023-07-03 12:27:45 +02:00
Lili (Tlapka) d484a0a363 fix reset_milestone 2023-07-03 12:18:29 +02:00
Lili (Tlapka) 348e674f74 make price tab in dashboard blank for milestones 2023-07-03 11:02:09 +02:00
Lili (Tlapka) ca9aa6d79d add reset and hard-reset command to init 2023-07-03 10:46:35 +02:00
Lili (Tlapka) 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
7 changed files with 78 additions and 47 deletions

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

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=[

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():

View File

@ -35,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():
@ -43,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():
@ -59,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():
@ -69,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():
@ -90,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:
@ -114,26 +122,29 @@ 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): def reset_milestone(milestone):
if not redeem_name in current_app.config['REDEEMS']: if not milestone in current_app.config['REDEEMS']:
print(f"Failed resetting milestone, {milestone} not in redeems file.") print(f"Failed resetting milestone, {milestone} not in redeems file.")
return None return False
try: try:
db = get_db()
db.execute( db.execute(
"DELETE FROM milestones WHERE name = ?", "DELETE FROM milestones WHERE name = ?",
(milestone,) (milestone,)
) )
db.execute( db.execute(
"INSERT INTO milestones(name, progress, goal) VALUES(?, ?, ?)", "INSERT INTO milestones(name, progress, goal, complete) VALUES(?, ?, ?, FALSE)",
(milestone, 0, current_app.config['REDEEMS'][milestone]['goal']) (milestone, 0, current_app.config['REDEEMS'][milestone]['goal'])
) )
db.commit() db.commit()
return True return True
except Error as e: except sqlite3.Error as e:
current_app.logger.error(f"Error occured adding a milestone: {e.args[0]}") current_app.logger.error(f"Error occured adding a milestone: {e.args[0]}")
return None return False
@ -141,16 +152,16 @@ def reset_milestone(milestone):
@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')
@ -158,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')
@ -176,31 +186,29 @@ 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.command('reset-milestone')
@click.argument('milestone') @click.argument('milestone')
def reset_milestone_command(milestone): def reset_milestone_command(milestone):
"""Resets a completed milestone back to zero.""" """Resets a completed milestone back to zero."""
if milestone_complete(milestone): if milestone_complete(get_db(), milestone):
if reset_milestone(milestone): if reset_milestone(milestone):
click.echo(f"Reset milestone {milestone}.") click.echo(f"Reset milestone {milestone}.")
else:
click.echo(f"Resetting milestone {milestone} failed.")
else: else:
click.echo(f"Could not reset milestone {milestone}, milestone not completed.") 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.echo("(You can hard-reset-milestone if you really want to reset it.)")
@click.command('hard-reset-milestone') @click.command('hard-reset-milestone')
@click.argument('milestone') @click.argument('milestone')
def hard_reset_milestone_command(milestone): def hard_reset_milestone_command(milestone):
"""Resets any milestone back to zero.""" """Resets any milestone back to zero."""
if reset_milestone(milestone): if reset_milestone(milestone):
click.echo(f"Hard reset milestone {milestone}.") click.echo(f"Hard reset milestone {milestone}.")
else:
click.echo(f"Hard resetting milestone {milestone} failed.")
def init_app(app): def init_app(app):
app.teardown_appcontext(close_db) app.teardown_appcontext(close_db)

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": {"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"]}
} }

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,20 +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) > 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.")

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>