Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2284fb3a00 | ||
|
|
3fe454d35c | ||
|
|
f1a57c16e4 | ||
|
|
7da002eb00 | ||
|
|
5e7810eb9f | ||
|
|
61d318f418 | ||
|
|
ba30c96c01 | ||
|
|
6d3a4671cc | ||
|
|
384ca03e23 | ||
|
|
7722bdf608 | ||
|
|
7dfc8faae7 | ||
|
|
8e4d21c02f | ||
|
|
571aa47aab | ||
|
|
433564bddf | ||
|
|
1abab80da4 | ||
|
|
894ff175a6 | ||
|
|
6641121ca3 | ||
|
|
7ffb1a15b0 | ||
|
|
e5e88bf520 | ||
|
|
b4a6c4e41c | ||
|
|
8c19088640 | ||
|
|
5316f7e205 | ||
|
|
31426fd851 | ||
|
|
8d5a3cda42 | ||
|
|
e237e7f885 | ||
|
|
80037593a9 | ||
|
|
d484a0a363 | ||
|
|
348e674f74 | ||
|
|
ca9aa6d79d | ||
|
|
ae42a052de | ||
|
|
d1416d3706 | ||
|
|
f66b125ac3 | ||
|
|
6f6fc10d0f | ||
|
|
bf68f518da |
@@ -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,22 @@ 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 decide the chat command for the redeem.
|
||||||
|
The redeem names shouldn't have spaces in them.
|
||||||
|
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
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from setuptools import find_packages, setup
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='tlapbot',
|
name='tlapbot',
|
||||||
version='1.2.0',
|
version='1.2.2',
|
||||||
packages=find_packages(),
|
packages=find_packages(),
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
install_requires=[
|
install_requires=[
|
||||||
|
|||||||
+9
-1
@@ -4,7 +4,7 @@ from flask import Flask
|
|||||||
from apscheduler.schedulers.background import BackgroundScheduler
|
from apscheduler.schedulers.background import BackgroundScheduler
|
||||||
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
|
|
||||||
|
|
||||||
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)
|
||||||
@@ -36,6 +36,12 @@ def create_app(test_config=None):
|
|||||||
raise RuntimeError("Prefix is >1 character. "
|
raise RuntimeError("Prefix is >1 character. "
|
||||||
"Change your config to set 1-character prefix.")
|
"Change your config to set 1-character prefix.")
|
||||||
|
|
||||||
|
# Check for spaces in redeems (they won't work)
|
||||||
|
for redeem in app.config['REDEEMS']:
|
||||||
|
if ' ' in redeem:
|
||||||
|
app.logger.warning(f"Redeem '{redeem}' has spaces in its name.")
|
||||||
|
app.logger.warning("Redeems with spaces are impossible to redeem.")
|
||||||
|
|
||||||
# prepare webhooks and redeem dashboard blueprints
|
# prepare webhooks and redeem dashboard blueprints
|
||||||
from . import owncast_webhooks
|
from . import owncast_webhooks
|
||||||
from . import tlapbot_dashboard
|
from . import tlapbot_dashboard
|
||||||
@@ -49,6 +55,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():
|
||||||
|
|||||||
+58
-8
@@ -4,6 +4,8 @@ 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 +36,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 +46,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 +63,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 +75,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 +98,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,15 +123,36 @@ 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 milestone not 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.')
|
||||||
|
|
||||||
|
|
||||||
@@ -129,7 +160,7 @@ def init_db_command():
|
|||||||
@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.')
|
||||||
|
|
||||||
|
|
||||||
@@ -138,7 +169,7 @@ 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.')
|
||||||
|
|
||||||
|
|
||||||
@@ -146,8 +177,7 @@ def refresh_counters_command():
|
|||||||
@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.')
|
||||||
|
|
||||||
|
|
||||||
@@ -156,10 +186,30 @@ 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):
|
||||||
app.teardown_appcontext(close_db)
|
app.teardown_appcontext(close_db)
|
||||||
app.cli.add_command(init_db_command)
|
app.cli.add_command(init_db_command)
|
||||||
|
|||||||
@@ -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"]}
|
||||||
}
|
}
|
||||||
+17
-10
@@ -4,20 +4,27 @@ from tlapbot.owncast_requests import send_chat
|
|||||||
|
|
||||||
def send_help():
|
def send_help():
|
||||||
message = []
|
message = []
|
||||||
message.append("Tlapbot gives you points for being in chat, and then allows you to spend those points.\n")
|
message.append("Tlapbot gives you points for being in chat, and then allows you to spend those points. <br>")
|
||||||
message.append(f"People connected to chat receive {current_app.config['POINTS_AMOUNT_GIVEN']} points every {current_app.config['POINTS_CYCLE_TIME']} seconds.\n")
|
message.append(f"People connected to chat receive {current_app.config['POINTS_AMOUNT_GIVEN']} points every {current_app.config['POINTS_CYCLE_TIME']} seconds. <br>")
|
||||||
message.append("You can see your points and recent redeems in the Tlapbot dashboard. Look for a button to click under the stream window.\n")
|
message.append("You can see your points and recent redeems in the Tlapbot dashboard. Look for a button to click under the stream window. <br>")
|
||||||
message.append("""Tlapbot commands:
|
message.append("""Tlapbot commands: <br>
|
||||||
!help to see this help message.
|
!help to see this help message. <br>
|
||||||
!points to see your points.\n"""
|
!points to see your points. <br>"""
|
||||||
)
|
)
|
||||||
if current_app.config['LIST_REDEEMS']:
|
if current_app.config['LIST_REDEEMS']:
|
||||||
message.append("Active redeems:\n")
|
message.append("Active redeems: <br>")
|
||||||
for redeem, redeem_info in current_app.config['REDEEMS'].items():
|
for redeem, redeem_info in current_app.config['REDEEMS'].items():
|
||||||
if 'info' in redeem_info:
|
if redeem_info.get('category', None):
|
||||||
message.append(f"!{redeem} for {redeem_info['price']} points. {redeem_info['info']}\n")
|
if not set(redeem_info['category']).intersection(set(current_app.config['ACTIVE_CATEGORIES'])):
|
||||||
|
continue
|
||||||
|
if 'type' in redeem_info and redeem_info['type'] == 'milestone':
|
||||||
|
message.append(f"!{redeem} milestone with goal of {redeem_info['goal']}.")
|
||||||
else:
|
else:
|
||||||
message.append(f"!{redeem} for {redeem_info['price']} points.\n")
|
message.append(f"!{redeem} for {redeem_info['price']} points.")
|
||||||
|
if 'info' in redeem_info:
|
||||||
|
message.append(f" {redeem_info['info']} <br>")
|
||||||
|
else:
|
||||||
|
message.append("<br>")
|
||||||
else:
|
else:
|
||||||
message.append("Check the dashboard for a list of currently active redeems.")
|
message.append("Check the dashboard for a list of currently active redeems.")
|
||||||
send_chat(''.join(message))
|
send_chat(''.join(message))
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from flask import current_app
|
|||||||
from sqlite3 import Error
|
from sqlite3 import Error
|
||||||
from re import sub
|
from re import sub
|
||||||
|
|
||||||
|
|
||||||
# # # db stuff # # #
|
# # # db stuff # # #
|
||||||
def read_users_points(db, user_id):
|
def read_users_points(db, user_id):
|
||||||
"""Errors out if user doesn't exist."""
|
"""Errors out if user doesn't exist."""
|
||||||
@@ -97,7 +98,7 @@ def add_user_to_database(db, user_id, display_name):
|
|||||||
|
|
||||||
def change_display_name(db, user_id, new_name):
|
def change_display_name(db, user_id, new_name):
|
||||||
try:
|
try:
|
||||||
cursor = db.execute(
|
db.execute(
|
||||||
"UPDATE points SET name = ? WHERE id = ?",
|
"UPDATE points SET name = ? WHERE id = ?",
|
||||||
(new_name, user_id)
|
(new_name, user_id)
|
||||||
)
|
)
|
||||||
@@ -109,7 +110,7 @@ def change_display_name(db, user_id, new_name):
|
|||||||
|
|
||||||
def remove_duplicate_usernames(db, user_id, username):
|
def remove_duplicate_usernames(db, user_id, username):
|
||||||
try:
|
try:
|
||||||
cursor = db.execute(
|
db.execute(
|
||||||
"""UPDATE points
|
"""UPDATE points
|
||||||
SET name = NULL
|
SET name = NULL
|
||||||
WHERE name = ? AND NOT id = ?""",
|
WHERE name = ? AND NOT id = ?""",
|
||||||
@@ -121,6 +122,7 @@ def remove_duplicate_usernames(db, user_id, username):
|
|||||||
|
|
||||||
|
|
||||||
# # # misc. stuff # # #
|
# # # misc. stuff # # #
|
||||||
|
# This is now unused since rawBody attribute of the webhook now returns cleaned-up emotes.
|
||||||
def remove_emoji(message):
|
def remove_emoji(message):
|
||||||
return sub(
|
return sub(
|
||||||
r'<img class="emoji" alt="(:.*?:)" title=":.*?:" src="/img/emoji/.*?">',
|
r'<img class="emoji" alt="(:.*?:)" title=":.*?:" src="/img/emoji/.*?">',
|
||||||
|
|||||||
@@ -16,17 +16,32 @@ 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']}
|
||||||
|
try:
|
||||||
r = requests.get(url, headers=headers)
|
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']}
|
||||||
|
try:
|
||||||
r = requests.post(url, headers=headers, json={"body": message})
|
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()
|
||||||
|
|
||||||
|
|||||||
@@ -36,22 +36,22 @@ def owncast_webhook():
|
|||||||
user_id = data["eventData"]["user"]["id"]
|
user_id = data["eventData"]["user"]["id"]
|
||||||
display_name = data["eventData"]["user"]["displayName"]
|
display_name = data["eventData"]["user"]["displayName"]
|
||||||
current_app.logger.debug(f'New chat message from {display_name}:')
|
current_app.logger.debug(f'New chat message from {display_name}:')
|
||||||
current_app.logger.debug(f'{data["eventData"]["body"]}')
|
current_app.logger.debug(f'{data["eventData"]["rawBody"]}')
|
||||||
if data["eventData"]["body"].startswith(f"{prefix}help"):
|
if data["eventData"]["rawBody"].startswith(f"{prefix}help"):
|
||||||
send_help()
|
send_help()
|
||||||
elif data["eventData"]["body"].startswith(f"{prefix}points"):
|
elif data["eventData"]["rawBody"].startswith(f"{prefix}points"):
|
||||||
points = read_users_points(db, user_id)
|
points = read_users_points(db, user_id)
|
||||||
if points is None:
|
if points is None:
|
||||||
send_chat("Error reading points.")
|
send_chat("Error reading points.")
|
||||||
else:
|
else:
|
||||||
send_chat(f"{display_name}'s points: {points}")
|
send_chat(f"{display_name}'s points: {points}")
|
||||||
elif data["eventData"]["body"].startswith(f"{prefix}name_update"):
|
elif data["eventData"]["rawBody"].startswith(f"{prefix}name_update"):
|
||||||
# Forces name update in case bot didn't catch the NAME_CHANGE
|
# Forces name update in case bot didn't catch the NAME_CHANGE
|
||||||
# event. Also removes saved usernames from users with same name
|
# event. Also removes saved usernames from users with same name
|
||||||
# if user is authenticated.
|
# if user is authenticated.
|
||||||
change_display_name(db, user_id, display_name)
|
change_display_name(db, user_id, display_name)
|
||||||
if data["eventData"]["user"]["authenticated"]:
|
if data["eventData"]["user"]["authenticated"]:
|
||||||
remove_duplicate_usernames(db, user_id, display_name)
|
remove_duplicate_usernames(db, user_id, display_name)
|
||||||
elif data["eventData"]["body"].startswith(prefix):
|
elif data["eventData"]["rawBody"].startswith(prefix):
|
||||||
handle_redeem(data["eventData"]["body"], user_id)
|
handle_redeem(data["eventData"]["rawBody"], user_id)
|
||||||
return data
|
return data
|
||||||
|
|||||||
+3
-21
@@ -23,7 +23,7 @@ def counter_exists(db, counter_name):
|
|||||||
def add_to_counter(db, counter_name):
|
def add_to_counter(db, counter_name):
|
||||||
if counter_exists(db, counter_name):
|
if counter_exists(db, counter_name):
|
||||||
try:
|
try:
|
||||||
cursor = db.execute(
|
db.execute(
|
||||||
"UPDATE counters SET count = count + 1 WHERE name = ?",
|
"UPDATE counters SET count = count + 1 WHERE name = ?",
|
||||||
(counter_name,)
|
(counter_name,)
|
||||||
)
|
)
|
||||||
@@ -37,7 +37,7 @@ def add_to_counter(db, counter_name):
|
|||||||
|
|
||||||
def add_to_redeem_queue(db, user_id, redeem_name, note=None):
|
def add_to_redeem_queue(db, user_id, redeem_name, note=None):
|
||||||
try:
|
try:
|
||||||
cursor = db.execute(
|
db.execute(
|
||||||
"INSERT INTO redeem_queue(redeem, redeemer_id, note) VALUES(?, ?, ?)",
|
"INSERT INTO redeem_queue(redeem, redeemer_id, note) VALUES(?, ?, ?)",
|
||||||
(redeem_name, user_id, note)
|
(redeem_name, user_id, note)
|
||||||
)
|
)
|
||||||
@@ -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(
|
||||||
@@ -177,7 +159,7 @@ def all_active_milestones(db):
|
|||||||
return all_active_milestones
|
return all_active_milestones
|
||||||
|
|
||||||
|
|
||||||
def all_active_redeems(db):
|
def all_active_redeems():
|
||||||
redeems = current_app.config['REDEEMS']
|
redeems = current_app.config['REDEEMS']
|
||||||
all_active_redeems = {}
|
all_active_redeems = {}
|
||||||
for redeem_name, redeem_dict in redeems.items():
|
for redeem_name, redeem_dict in redeems.items():
|
||||||
|
|||||||
+24
-20
@@ -3,7 +3,7 @@ from tlapbot.db import get_db
|
|||||||
from tlapbot.owncast_requests import send_chat
|
from tlapbot.owncast_requests import send_chat
|
||||||
from tlapbot.redeems import (add_to_redeem_queue, add_to_counter, add_to_milestone,
|
from tlapbot.redeems import (add_to_redeem_queue, add_to_counter, add_to_milestone,
|
||||||
check_apply_milestone_completion, milestone_complete, is_redeem_active)
|
check_apply_milestone_completion, milestone_complete, is_redeem_active)
|
||||||
from tlapbot.owncast_helpers import use_points, read_users_points, remove_emoji
|
from tlapbot.owncast_helpers import use_points, read_users_points
|
||||||
|
|
||||||
|
|
||||||
def handle_redeem(message, user_id):
|
def handle_redeem(message, user_id):
|
||||||
@@ -22,10 +22,31 @@ 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 a positive integer.")
|
||||||
|
elif int(note) > points:
|
||||||
|
send_chat(f"Can't redeem {redeem}, you're donating more points than you have.")
|
||||||
|
elif int(note) == 0:
|
||||||
|
send_chat(f"Can't donate zero points.")
|
||||||
|
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
|
||||||
@@ -45,27 +66,10 @@ def handle_redeem(message, user_id):
|
|||||||
if not note:
|
if not note:
|
||||||
send_chat(f"Cannot redeem {redeem}, no note included.")
|
send_chat(f"Cannot redeem {redeem}, no note included.")
|
||||||
return
|
return
|
||||||
if (add_to_redeem_queue(db, user_id, redeem, remove_emoji(note)) and
|
if (add_to_redeem_queue(db, user_id, redeem, note) and
|
||||||
use_points(db, user_id, price)):
|
use_points(db, user_id, price)):
|
||||||
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.")
|
||||||
|
|||||||
@@ -30,12 +30,14 @@
|
|||||||
<th>Your points balance</th>
|
<th>Your points balance</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
{% for user in users %}
|
|
||||||
<tbody>
|
<tbody>
|
||||||
|
{% for user in users %}
|
||||||
|
<tr>
|
||||||
<td> {{ user[0] }} </td>
|
<td> {{ user[0] }} </td>
|
||||||
<td> {{ user[1] }} </td>
|
<td> {{ user[1] }} </td>
|
||||||
</tbody>
|
</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>
|
||||||
|
{% for counter in counters %}
|
||||||
|
<tr>
|
||||||
<td> {{ counter[0] }} </td>
|
<td> {{ counter[0] }} </td>
|
||||||
<td> {{ counter[1] }} </td>
|
<td> {{ counter[1] }} </td>
|
||||||
</tbody>
|
</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>
|
||||||
|
{% for milestone in milestones %}
|
||||||
|
<tr>
|
||||||
<td> {{ milestone[0] }} </td>
|
<td> {{ milestone[0] }} </td>
|
||||||
<td> <progress id="file" max={{ milestone[2] }} value={{ milestone[1] }}></progress></td>
|
<td> <progress id="file" max={{ milestone[2] }} value={{ milestone[1] }}></progress></td>
|
||||||
<td> {{ milestone[1] }} / {{ milestone[2] }}</td>
|
<td> {{ milestone[1] }} / {{ milestone[2] }}</td>
|
||||||
</tbody>
|
</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>
|
||||||
|
{% for row in queue %}
|
||||||
|
<tr>
|
||||||
<td>{{ row[0].replace(tzinfo=utc_timezone).astimezone().strftime("%H:%M") }}</td>
|
<td>{{ row[0].replace(tzinfo=utc_timezone).astimezone().strftime("%H:%M") }}</td>
|
||||||
<td>{{ row[1] }}</td>
|
<td>{{ row[1] }}</td>
|
||||||
<td>{{ row[3] }}</td>
|
<td>{{ row[3] }}</td>
|
||||||
{% if row[2] %}
|
{% if row[2] %}
|
||||||
<td>{{ row[2] }}</td>
|
<td>{{ row[2] }}</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</tbody>
|
</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>
|
||||||
|
{% for redeem, redeem_info in redeems.items() %}
|
||||||
|
<tr>
|
||||||
<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>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</tbody>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from flask import render_template, Blueprint, request, current_app
|
|||||||
from tlapbot.db import get_db
|
from tlapbot.db import get_db
|
||||||
from tlapbot.redeems import all_active_counters, all_active_milestones, all_active_redeems, pretty_redeem_queue
|
from tlapbot.redeems import all_active_counters, all_active_milestones, all_active_redeems, pretty_redeem_queue
|
||||||
from tlapbot.owncast_helpers import read_all_users_with_username
|
from tlapbot.owncast_helpers import read_all_users_with_username
|
||||||
from datetime import datetime, timezone
|
from datetime import timezone
|
||||||
|
|
||||||
bp = Blueprint('redeem_dashboard', __name__)
|
bp = Blueprint('redeem_dashboard', __name__)
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ def dashboard():
|
|||||||
queue=pretty_redeem_queue(db),
|
queue=pretty_redeem_queue(db),
|
||||||
counters=all_active_counters(db),
|
counters=all_active_counters(db),
|
||||||
milestones=all_active_milestones(db),
|
milestones=all_active_milestones(db),
|
||||||
redeems=all_active_redeems(db),
|
redeems=all_active_redeems(),
|
||||||
prefix=current_app.config['PREFIX'],
|
prefix=current_app.config['PREFIX'],
|
||||||
passive=current_app.config['PASSIVE'],
|
passive=current_app.config['PASSIVE'],
|
||||||
username=username,
|
username=username,
|
||||||
|
|||||||
Reference in New Issue
Block a user