Compare commits
67
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
acbc93bedd | ||
|
|
e5e88bf520 | ||
|
|
b4a6c4e41c | ||
|
|
8c19088640 | ||
|
|
5316f7e205 | ||
|
|
31426fd851 | ||
|
|
8d5a3cda42 | ||
|
|
e237e7f885 | ||
|
|
80037593a9 | ||
|
|
d484a0a363 | ||
|
|
348e674f74 | ||
|
|
ca9aa6d79d | ||
|
|
ae42a052de | ||
|
|
d1416d3706 | ||
|
|
f66b125ac3 | ||
|
|
6f6fc10d0f | ||
|
|
bf68f518da | ||
|
|
8e74da1f26 | ||
|
|
b08371838f | ||
|
|
739438f28b | ||
|
|
d372ed6e6b | ||
|
|
1b02624dea | ||
|
|
265a6cc9b4 | ||
|
|
021737ad16 | ||
|
|
6c2eb41ec3 | ||
|
|
c68a3924fc | ||
|
|
6e500c962d | ||
|
|
0b84e06c52 | ||
|
|
b985a58b8f | ||
|
|
19f8e28780 | ||
|
|
99e014b2d1 | ||
|
|
5f0aa468c7 | ||
|
|
1937c5d660 | ||
|
|
3858a14f1a | ||
|
|
ea1fc6e051 | ||
|
|
0176b6e84c | ||
|
|
7b31dbcb31 | ||
|
|
6248a2c546 | ||
|
|
44eb0be6e2 | ||
|
|
abbc099661 | ||
|
|
7a37f8afcf | ||
|
|
f8a174b94f | ||
|
|
b8e465e50c | ||
|
|
26c94a65a9 | ||
|
|
10e84d4aab | ||
|
|
7235340a39 | ||
|
|
d6297c537f | ||
|
|
e1ef0c4619 | ||
|
|
49053ee4f4 | ||
|
|
9d910e58ce | ||
|
|
583aeedf64 | ||
|
|
423f12ed34 | ||
|
|
7e408c1b74 | ||
|
|
cf8376eebc | ||
|
|
c203ce132f | ||
|
|
22501c80c0 | ||
|
|
54ea10a43c | ||
|
|
9019f98031 | ||
|
|
e8b9ca3cda | ||
|
|
9323b58ed6 | ||
|
|
cb574618a6 | ||
|
|
68b8588c76 | ||
|
|
2732967e26 | ||
|
|
07c701de71 | ||
|
|
90615ce7c6 | ||
|
|
82d9088c18 | ||
|
|
189ffcc1a8 |
@@ -2,7 +2,7 @@
|
|||||||
Tlapbot is an [Owncast](https://owncast.online/) bot that adds channel points and
|
Tlapbot is an [Owncast](https://owncast.online/) bot that adds channel points and
|
||||||
channel point redeems to your Owncast page.
|
channel point redeems to your Owncast page.
|
||||||
|
|
||||||
This bot is currently in-development. The goal is to have an experience similar
|
The goal is to have an experience similar
|
||||||
to [Twitch channel points](https://help.twitch.tv/s/article/viewer-channel-point-guide) by making use of [Owncast webhooks](https://owncast.online/thirdparty/webhooks/) and
|
to [Twitch channel points](https://help.twitch.tv/s/article/viewer-channel-point-guide) by making use of [Owncast webhooks](https://owncast.online/thirdparty/webhooks/) and
|
||||||
[External actions](https://owncast.online/thirdparty/actions/).
|
[External actions](https://owncast.online/thirdparty/actions/).
|
||||||
## Features
|
## Features
|
||||||
@@ -27,6 +27,31 @@ Tlapbot has these basic commands:
|
|||||||
use the new prefix instead.)
|
use the new prefix instead.)
|
||||||
|
|
||||||
Tlapbot also automatically adds a command for each redeem in the redeems file.
|
Tlapbot also automatically adds a command for each redeem in the redeems file.
|
||||||
|
### Passive mode
|
||||||
|
Tlapbot can also be run in passive mode. In passive mode, no redeems will be available, and Tlapbot will not send any messages.
|
||||||
|
|
||||||
|
However, it will still give points to viewers, and track username changes.
|
||||||
|
|
||||||
|
The Tlapbot dashboard will display a passive mode disclaimer instead of redeems.
|
||||||
|
|
||||||
|
### Tlapbot dashboard
|
||||||
|
Tlapbot dashboard is a standalone page available at `/dashboard`, made to be easily viewable as an owncast external action. The Tlapbot dashboard shows all redeems and active counters.
|
||||||
|
|
||||||
|
Counters are at the top, followed by a list of active milestones and their progress.
|
||||||
|
|
||||||
|
Tlapbot dashboard also shows the chatter's points balance when they open it as an external action.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
*Tlapbot dashboard when viewed as an external action.*
|
||||||
|
#### Redeem queue tab
|
||||||
|
The redeem queue shows a chronological list of note and list redeems with timestamps, the redeemer's username, and the note sent.
|
||||||
|

|
||||||
|
|
||||||
|
*Redeem queue tab.*
|
||||||
|
#### Redeems help tab
|
||||||
|
The dashboard also has a "Redeems help" tab. It shows an explanation of redeem types,
|
||||||
|
and lists all active redeems, along with their price, type and description.
|
||||||
### Tlapbot redeems types
|
### Tlapbot redeems types
|
||||||
Tlapbot currently supports three different redeem types. Each type of a redeem
|
Tlapbot currently supports three different redeem types. Each type of a redeem
|
||||||
works slightly differently, and displays differently on the redeems dashboard.
|
works slightly differently, and displays differently on the redeems dashboard.
|
||||||
@@ -52,19 +77,19 @@ Instead, the tlapbot dashboard keeps a number for each "counter", and each redee
|
|||||||
|
|
||||||
Counter redeems can be used to gauge interest, tally up votes, or to keep track of how many emotes should be added to an OBS scene.
|
Counter redeems can be used to gauge interest, tally up votes, or to keep track of how many emotes should be added to an OBS scene.
|
||||||
|
|
||||||
### Tlapbot dashboard
|
#### Milestone
|
||||||
Tlapbot dashboard is a standalone page available at `/dashboard`, made to be easily viewable as an owncast external action. The Tlapbot dashboard shows all redeems and active counters.
|
Milestone redeems are long-term goals that are reset separately from other redeems.
|
||||||
|
Viewers donate variable amounts of points that add up together to fulfill the milestone goal.
|
||||||
|
|
||||||
Counters are at the top, followed by a chronological list of recent List and Note redeems.
|
Each milestone has a goal, a number of points required to send, and the points from
|
||||||
|
all users add together to progress and reach the goal.
|
||||||
|
|
||||||
Tlapbot dashboard also shows the chatter's points balance when they open it as an external action.
|
Milestones show as a progress bar on the dashboard.
|
||||||
|
|
||||||
|
Milestone redeems can be used as long-term community challenges, to start streamer
|
||||||
|
challenges, decide new games to play, etc.
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
*Tlapbot dashboard when viewed as an external action.*
|
|
||||||
#### Redeems help tab
|
|
||||||
The dashboard also has a "Redeems help" tab. It shows an explanation of redeem types,
|
|
||||||
and lists all active redeems, along with their price, type and description.
|
|
||||||
## License & Contributions
|
## License & Contributions
|
||||||
Tlapbot as it currently is does not come with a license. If you're a content creator, streamer, vtuber, etc. I'll be happy to give you permission to use Tlapbot, or make changes that'd fit your stream.
|
Tlapbot as it currently is does not come with a license. If you're a content creator, streamer, vtuber, etc. I'll be happy to give you permission to use Tlapbot, or make changes that'd fit your stream.
|
||||||
|
|
||||||
@@ -89,8 +114,8 @@ as a package in editable more.
|
|||||||
```bash
|
```bash
|
||||||
python -m flask init-db
|
python -m flask init-db
|
||||||
```
|
```
|
||||||
5. Create a `instance/config.py` file and fill it in as needed.
|
5. Create an `instance/config.py` file and fill it in as needed.
|
||||||
Default values are included in `tlapbot/default_config`, and values in
|
Default values are included in `tlapbot/default_config.py`, and values in
|
||||||
`config.py` overwrite them. (The database also lives in the instance folder
|
`config.py` overwrite them. (The database also lives in the instance folder
|
||||||
by default.)
|
by default.)
|
||||||
|
|
||||||
@@ -103,10 +128,13 @@ by default.)
|
|||||||
6. OPTIONAL: Create an `instance/redeems.py` file and add your custom redeems.
|
6. OPTIONAL: Create an `instance/redeems.py` file and add your custom redeems.
|
||||||
If you don't add a redeems file, the bot will initialize the default redeems from `tlapbot/default_redeems.py`.
|
If you don't add a redeems file, the bot will initialize the default redeems from `tlapbot/default_redeems.py`.
|
||||||
More details on how to write the config and redeems files are written later in the readme.
|
More details on how to write the config and redeems files are written later in the readme.
|
||||||
|
7. If you've added any new counters or milestones, run `refresh-counters` or `refresh-milestones` commands to initialize them into the database.
|
||||||
|
|
||||||
This installation is fine both for just running Tlapbot as it is, but it also works as a dev setup if you want to make changes or contribute.
|
This installation is fine both for just running Tlapbot as it is, but it also works as a dev setup if you want to make changes or contribute.
|
||||||
|
|
||||||
Updating should be as easy as `git pull`ing the new version.
|
Updating should be as easy as `git pull`ing the new version.
|
||||||
|
Sometimes, if an update adds new database tables or columns, you will also need to
|
||||||
|
rerun the `init-db` CLI command.
|
||||||
## Owncast configuration
|
## Owncast configuration
|
||||||
In the Owncast web interface, navigate to the admin interface at `/admin`,
|
In the Owncast web interface, navigate to the admin interface at `/admin`,
|
||||||
and then go to Integrations.
|
and then go to Integrations.
|
||||||
@@ -205,7 +233,8 @@ Tlapbot comes with a few Click CLI commands. The commands let you clear out coun
|
|||||||
#### init-db
|
#### init-db
|
||||||
The init-db command initializes the database.
|
The init-db command initializes the database.
|
||||||
|
|
||||||
**This command should only be run when first installing tlapbot.**
|
**This command should only be run when first installing tlapbot,
|
||||||
|
or when updating to a tlapbot version that changed the database schema.**
|
||||||
#### clear-queue
|
#### clear-queue
|
||||||
The clear-queue command clears the redeem queue and resets all active counters to zero.
|
The clear-queue command clears the redeem queue and resets all active counters to zero.
|
||||||
You should run this command if you're about to start a new stream, and want to start with empty counters and queue.
|
You should run this command if you're about to start a new stream, and want to start with empty counters and queue.
|
||||||
@@ -229,11 +258,34 @@ Does the same as `clear-queue` and `refresh-counters` together.
|
|||||||
python -m flask clear-refresh
|
python -m flask clear-refresh
|
||||||
```
|
```
|
||||||
Run this if you're adding/removing counters, want to reset them to zero and want to clear all redeems as well.
|
Run this if you're adding/removing counters, want to reset them to zero and want to clear all redeems as well.
|
||||||
|
#### refresh-milestones
|
||||||
|
Deletes old milestones and initializes new ones from the redeems file.
|
||||||
|
```bash
|
||||||
|
python -m flask refresh-milestones
|
||||||
|
```
|
||||||
|
Running this command shouldn't reset progress on milestones that are already in the database
|
||||||
|
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,
|
||||||
|
that's `instance/` from the root of the Github repository.
|
||||||
|
|
||||||
|
Take care not to replace `tlapbot/redeems.py` with your redeems config.
|
||||||
|
`tlapbot/redeems.py` contains functions that handle redeems interactions with the db,
|
||||||
|
and not the redeems config.
|
||||||
### config.py
|
### config.py
|
||||||
Values you can include in `config.py` to change how the bot behaves.
|
Values you can include in `config.py` to change how the bot behaves.
|
||||||
|
|
||||||
(`config.py` should be in the instance folder: `/instance/config.py` for folder install.)
|
(`config.py` should be in the instance folder: `instance/config.py` for folder install.)
|
||||||
#### Mandatory
|
#### Mandatory
|
||||||
Including these values is mandatory if you want tlapbot to work.
|
Including these values is mandatory if you want tlapbot to work.
|
||||||
- `SECRET_KEY` is your secret key. Get one from running `python -c 'import secrets; print(secrets.token_hex())'`
|
- `SECRET_KEY` is your secret key. Get one from running `python -c 'import secrets; print(secrets.token_hex())'`
|
||||||
@@ -244,6 +296,7 @@ Including these values will overwrite their defaults from `/tlapbot/default_conf
|
|||||||
- `POINTS_CYCLE_TIME` decides how often channel points are given to users in chat,
|
- `POINTS_CYCLE_TIME` decides how often channel points are given to users in chat,
|
||||||
in seconds.
|
in seconds.
|
||||||
- `POINTS_AMOUNT_GIVEN` decides how many channel points users receive.
|
- `POINTS_AMOUNT_GIVEN` decides how many channel points users receive.
|
||||||
|
- `PASSIVE` if `True`, sets Tlapbot into passive mode, where no redeems are available. The bot will still track username changes and give out points.
|
||||||
- `LIST_REDEEMS` if `True`, all redeems will be listed after the `!help` command in chat.
|
- `LIST_REDEEMS` if `True`, all redeems will be listed after the `!help` command in chat.
|
||||||
This makes the !help output quite long, so it's `False` by default.
|
This makes the !help output quite long, so it's `False` by default.
|
||||||
- `GUNICORN` if `True`, sets logging to use gunicorn's logger. Only set this to True if you're using Gunicorn to run tlapbot.
|
- `GUNICORN` if `True`, sets logging to use gunicorn's logger. Only set this to True if you're using Gunicorn to run tlapbot.
|
||||||
@@ -262,7 +315,7 @@ ACTIVE_CATEGORIES=["gaming"]
|
|||||||
### redeems.py
|
### redeems.py
|
||||||
`redeems.py` is a file where you define all your custom redeems. Tlapbot will work without it, but it will load a few default, generic redeems from `tlapbot/default_redeems.py`.
|
`redeems.py` is a file where you define all your custom redeems. Tlapbot will work without it, but it will load a few default, generic redeems from `tlapbot/default_redeems.py`.
|
||||||
|
|
||||||
(`redeems.py` should be in the instance folder: `/instance/redeems.py` for folder install.)
|
(`redeems.py` should be in the instance folder: `instance/redeems.py` for folder install.)
|
||||||
#### `default_redeems.py`:
|
#### `default_redeems.py`:
|
||||||
```python
|
```python
|
||||||
REDEEMS={
|
REDEEMS={
|
||||||
@@ -270,16 +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": {"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"`.
|
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.
|
- `"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"` or `"note"`. 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.
|
- `"goal"` is a required field for milestone goals. It should be an integer, deciding the amount of points required to complete the milestone.
|
||||||
- `"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).
|
- `"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.1.0',
|
version='1.2.1',
|
||||||
packages=find_packages(),
|
packages=find_packages(),
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
install_requires=[
|
install_requires=[
|
||||||
|
|||||||
+15
-8
@@ -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_helpers import (is_stream_live, give_points_to_chat,
|
from tlapbot.owncast_requests import is_stream_live, give_points_to_chat
|
||||||
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)
|
||||||
@@ -24,10 +27,6 @@ def create_app(test_config=None):
|
|||||||
app.config.from_object('tlapbot.default_redeems')
|
app.config.from_object('tlapbot.default_redeems')
|
||||||
app.config.from_pyfile('config.py', silent=True)
|
app.config.from_pyfile('config.py', silent=True)
|
||||||
app.config.from_pyfile('redeems.py', silent=True)
|
app.config.from_pyfile('redeems.py', silent=True)
|
||||||
app.config.from_mapping(
|
|
||||||
REDEEMS=remove_inactive_redeems(app.config['REDEEMS'],
|
|
||||||
app.config['ACTIVE_CATEGORIES'])
|
|
||||||
)
|
|
||||||
|
|
||||||
# Make logging work for gunicorn-ran instances of tlapbot.
|
# Make logging work for gunicorn-ran instances of tlapbot.
|
||||||
if app.config['GUNICORN']:
|
if app.config['GUNICORN']:
|
||||||
@@ -42,9 +41,9 @@ def create_app(test_config=None):
|
|||||||
|
|
||||||
# prepare webhooks and redeem dashboard blueprints
|
# prepare webhooks and redeem dashboard blueprints
|
||||||
from . import owncast_webhooks
|
from . import owncast_webhooks
|
||||||
from . import owncast_redeem_dashboard
|
from . import tlapbot_dashboard
|
||||||
app.register_blueprint(owncast_webhooks.bp)
|
app.register_blueprint(owncast_webhooks.bp)
|
||||||
app.register_blueprint(owncast_redeem_dashboard.bp)
|
app.register_blueprint(tlapbot_dashboard.bp)
|
||||||
|
|
||||||
# add db CLI commands
|
# add db CLI commands
|
||||||
from . import db
|
from . import db
|
||||||
@@ -52,14 +51,22 @@ def create_app(test_config=None):
|
|||||||
app.cli.add_command(db.clear_queue_command)
|
app.cli.add_command(db.clear_queue_command)
|
||||||
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.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
|
||||||
|
|||||||
+115
-20
@@ -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:
|
||||||
@@ -32,8 +33,10 @@ def insert_counters(db):
|
|||||||
(redeem,)
|
(redeem,)
|
||||||
)
|
)
|
||||||
db.commit()
|
db.commit()
|
||||||
except 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():
|
||||||
@@ -56,8 +60,10 @@ def clear_redeem_queue():
|
|||||||
"""UPDATE counters SET count = 0"""
|
"""UPDATE counters SET count = 0"""
|
||||||
)
|
)
|
||||||
db.commit()
|
db.commit()
|
||||||
except 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():
|
||||||
@@ -66,35 +72,96 @@ def refresh_counters():
|
|||||||
try:
|
try:
|
||||||
db.execute("DELETE FROM counters")
|
db.execute("DELETE FROM counters")
|
||||||
db.commit()
|
db.commit()
|
||||||
except 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])
|
||||||
|
return False
|
||||||
|
if insert_counters(db):
|
||||||
|
return True
|
||||||
|
|
||||||
for redeem, redeem_info in current_app.config['REDEEMS'].items():
|
|
||||||
if redeem_info["type"] == "counter":
|
def refresh_milestones():
|
||||||
try:
|
db = get_db()
|
||||||
|
# delete old milestones
|
||||||
|
try:
|
||||||
|
cursor = db.execute("SELECT name FROM milestones")
|
||||||
|
milestones = cursor.fetchall()
|
||||||
|
to_delete = []
|
||||||
|
for milestone in milestones:
|
||||||
|
milestone = milestone[0]
|
||||||
|
if milestone not in current_app.config['REDEEMS'].keys():
|
||||||
|
to_delete.append(milestone)
|
||||||
|
elif current_app.config['REDEEMS'][milestone]['type'] != "milestone":
|
||||||
|
to_delete.append(milestone)
|
||||||
|
for milestone in to_delete:
|
||||||
|
cursor.execute("DELETE FROM milestones WHERE name = ?", (milestone,))
|
||||||
|
db.commit()
|
||||||
|
except sqlite3.Error as e:
|
||||||
|
print("Failed deleting old milestones from db:", e.args[0])
|
||||||
|
return False
|
||||||
|
|
||||||
|
# add new milestones
|
||||||
|
try:
|
||||||
|
for redeem, redeem_info in current_app.config['REDEEMS'].items():
|
||||||
|
if redeem_info["type"] == "milestone":
|
||||||
cursor = db.execute(
|
cursor = db.execute(
|
||||||
"INSERT INTO counters(name, count) VALUES(?, 0)",
|
"SELECT goal FROM milestones WHERE name = ?",
|
||||||
(redeem,)
|
(redeem,)
|
||||||
)
|
)
|
||||||
db.commit()
|
result = cursor.fetchone()
|
||||||
except Error as e:
|
if result is None:
|
||||||
print("Failed inserting counters to db:", e.args[0])
|
cursor.execute(
|
||||||
|
"INSERT INTO milestones(name, progress, goal, complete) VALUES(?, 0, ?, FALSE)",
|
||||||
|
(redeem, redeem_info['goal'])
|
||||||
|
)
|
||||||
|
# update existing milestone to new goal
|
||||||
|
elif result != redeem_info["goal"]:
|
||||||
|
cursor.execute(
|
||||||
|
"UPDATE milestones SET goal = ? WHERE name = ?",
|
||||||
|
(redeem_info["goal"], redeem)
|
||||||
|
)
|
||||||
|
db.commit()
|
||||||
|
except sqlite3.Error as e:
|
||||||
|
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')
|
||||||
@@ -102,17 +169,45 @@ 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')
|
||||||
|
@with_appcontext
|
||||||
|
def refresh_milestones_command():
|
||||||
|
"""Initialize all milestones from the redeems file,
|
||||||
|
delete milestones not in redeem file."""
|
||||||
|
if refresh_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):
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ OWNCAST_ACCESS_TOKEN=''
|
|||||||
OWNCAST_INSTANCE_URL='http://localhost:8080'
|
OWNCAST_INSTANCE_URL='http://localhost:8080'
|
||||||
POINTS_CYCLE_TIME=600
|
POINTS_CYCLE_TIME=600
|
||||||
POINTS_AMOUNT_GIVEN=10
|
POINTS_AMOUNT_GIVEN=10
|
||||||
|
PASSIVE=False
|
||||||
LIST_REDEEMS=False
|
LIST_REDEEMS=False
|
||||||
ACTIVE_CATEGORIES=[]
|
ACTIVE_CATEGORIES=[]
|
||||||
GUNICORN=False
|
GUNICORN=False
|
||||||
|
|||||||
@@ -3,5 +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": {"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"]}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
from flask import current_app
|
from flask import current_app
|
||||||
from tlapbot.owncast_helpers import send_chat
|
from tlapbot.owncast_requests import send_chat
|
||||||
|
|
||||||
|
|
||||||
def send_help():
|
def send_help():
|
||||||
|
|||||||
+31
-120
@@ -1,33 +1,7 @@
|
|||||||
from flask import current_app
|
from flask import current_app
|
||||||
import requests
|
|
||||||
from sqlite3 import Error
|
from sqlite3 import Error
|
||||||
from re import sub
|
from re import sub
|
||||||
|
|
||||||
# # # requests stuff # # #
|
|
||||||
def is_stream_live():
|
|
||||||
url = current_app.config['OWNCAST_INSTANCE_URL'] + '/api/status'
|
|
||||||
r = requests.get(url)
|
|
||||||
return r.json()["online"]
|
|
||||||
|
|
||||||
|
|
||||||
def give_points_to_chat(db):
|
|
||||||
url = current_app.config['OWNCAST_INSTANCE_URL'] + '/api/integrations/clients'
|
|
||||||
headers = {"Authorization": "Bearer " + current_app.config['OWNCAST_ACCESS_TOKEN']}
|
|
||||||
r = requests.get(url, headers=headers)
|
|
||||||
unique_users = set(map(lambda user_object: user_object["user"]["id"], r.json()))
|
|
||||||
for user_id in unique_users:
|
|
||||||
give_points_to_user(db,
|
|
||||||
user_id,
|
|
||||||
current_app.config['POINTS_AMOUNT_GIVEN'])
|
|
||||||
|
|
||||||
|
|
||||||
def send_chat(message):
|
|
||||||
url = current_app.config['OWNCAST_INSTANCE_URL'] + '/api/integrations/chat/send'
|
|
||||||
headers = {"Authorization": "Bearer " + current_app.config['OWNCAST_ACCESS_TOKEN']}
|
|
||||||
r = requests.post(url, headers=headers, json={"body": message})
|
|
||||||
return r.json()
|
|
||||||
|
|
||||||
|
|
||||||
# # # 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."""
|
||||||
@@ -121,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(
|
||||||
@@ -133,86 +138,6 @@ def change_display_name(db, user_id, new_name):
|
|||||||
current_app.logger.error(f"To user id: {user_id}, with display name: {new_name}")
|
current_app.logger.error(f"To user id: {user_id}, with display name: {new_name}")
|
||||||
|
|
||||||
|
|
||||||
def check_counter_exists(db, counter_name):
|
|
||||||
try:
|
|
||||||
cursor = db.execute(
|
|
||||||
"SELECT count FROM counters WHERE name = ?",
|
|
||||||
(counter_name,)
|
|
||||||
)
|
|
||||||
counter = cursor.fetchone()
|
|
||||||
if counter is None:
|
|
||||||
current_app.logger.warning("Counter not found in database.")
|
|
||||||
current_app.logger.warning("Maybe you forgot to run the refresh-counters CLI command "
|
|
||||||
"after you added a new counter to the config?")
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
except Error as e:
|
|
||||||
current_app.logger.error(f"Error occured checking if counter exists: {e.args[0]}")
|
|
||||||
current_app.logger.error(f"For counter: {counter_name}")
|
|
||||||
|
|
||||||
|
|
||||||
def add_to_counter(db, counter_name):
|
|
||||||
if check_counter_exists(db, counter_name):
|
|
||||||
try:
|
|
||||||
cursor = db.execute(
|
|
||||||
"UPDATE counters SET count = count + 1 WHERE name = ?",
|
|
||||||
(counter_name,)
|
|
||||||
)
|
|
||||||
db.commit()
|
|
||||||
return True
|
|
||||||
except Error as e:
|
|
||||||
current_app.logger.error(f"Error occured adding to counter: {e.args[0]}")
|
|
||||||
current_app.logger.error(f"To counter: {counter_name}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def add_to_redeem_queue(db, user_id, redeem_name, note=None):
|
|
||||||
try:
|
|
||||||
cursor = db.execute(
|
|
||||||
"INSERT INTO redeem_queue(redeem, redeemer_id, note) VALUES(?, ?, ?)",
|
|
||||||
(redeem_name, user_id, note)
|
|
||||||
)
|
|
||||||
db.commit()
|
|
||||||
return True
|
|
||||||
except Error as e:
|
|
||||||
current_app.logger.error(f"Error occured adding to redeem queue: {e.args[0]}")
|
|
||||||
current_app.logger.error(f"To user: {user_id} with redeem: {redeem_name} with note: {note}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def all_counters(db):
|
|
||||||
try:
|
|
||||||
cursor = db.execute(
|
|
||||||
"""SELECT counters.name, counters.count FROM counters"""
|
|
||||||
)
|
|
||||||
return cursor.fetchall()
|
|
||||||
except Error as e:
|
|
||||||
current_app.logger.error(f"Error occured selecting all counters: {e.args[0]}")
|
|
||||||
|
|
||||||
|
|
||||||
def pretty_redeem_queue(db):
|
|
||||||
try:
|
|
||||||
cursor = db.execute(
|
|
||||||
"""SELECT redeem_queue.created, redeem_queue.redeem, redeem_queue.note, points.name
|
|
||||||
FROM redeem_queue
|
|
||||||
INNER JOIN points
|
|
||||||
on redeem_queue.redeemer_id = points.id"""
|
|
||||||
)
|
|
||||||
return cursor.fetchall()
|
|
||||||
except Error as e:
|
|
||||||
current_app.logger.error(f"Error occured selecting pretty redeem queue: {e.args[0]}")
|
|
||||||
|
|
||||||
|
|
||||||
def whole_redeem_queue(db):
|
|
||||||
try:
|
|
||||||
cursor = db.execute(
|
|
||||||
"SELECT * from redeem_queue"
|
|
||||||
)
|
|
||||||
return cursor.fetchall()
|
|
||||||
except Error as e:
|
|
||||||
current_app.logger.error(f"Error occured selecting redeem queue: {e.args[0]}")
|
|
||||||
|
|
||||||
|
|
||||||
def remove_duplicate_usernames(db, user_id, username):
|
def remove_duplicate_usernames(db, user_id, username):
|
||||||
try:
|
try:
|
||||||
cursor = db.execute(
|
cursor = db.execute(
|
||||||
@@ -233,17 +158,3 @@ def remove_emoji(message):
|
|||||||
r'\1',
|
r'\1',
|
||||||
message
|
message
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def is_redeem_active(redeem, active_categories):
|
|
||||||
if "category" in redeem[1] and redeem[1]["category"]:
|
|
||||||
for category in redeem[1]["category"]:
|
|
||||||
if category in active_categories:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def remove_inactive_redeems(redeems, active_categories):
|
|
||||||
return dict(filter(lambda redeem: is_redeem_active(redeem, active_categories),
|
|
||||||
redeems.items()))
|
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import requests
|
||||||
|
from flask import current_app
|
||||||
|
from tlapbot.owncast_helpers import give_points_to_user
|
||||||
|
|
||||||
|
|
||||||
|
def is_stream_live():
|
||||||
|
url = current_app.config['OWNCAST_INSTANCE_URL'] + '/api/status'
|
||||||
|
try:
|
||||||
|
r = requests.get(url)
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
current_app.logger.error(f"Error occured checking if stream is live: {e.args[0]}")
|
||||||
|
return False
|
||||||
|
return r.json()["online"]
|
||||||
|
|
||||||
|
|
||||||
|
def give_points_to_chat(db):
|
||||||
|
url = current_app.config['OWNCAST_INSTANCE_URL'] + '/api/integrations/clients'
|
||||||
|
headers = {"Authorization": "Bearer " + current_app.config['OWNCAST_ACCESS_TOKEN']}
|
||||||
|
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()))
|
||||||
|
for user_id in unique_users:
|
||||||
|
give_points_to_user(db, user_id, current_app.config['POINTS_AMOUNT_GIVEN'])
|
||||||
|
|
||||||
|
|
||||||
|
def send_chat(message):
|
||||||
|
url = current_app.config['OWNCAST_INSTANCE_URL'] + '/api/integrations/chat/send'
|
||||||
|
headers = {"Authorization": "Bearer " + current_app.config['OWNCAST_ACCESS_TOKEN']}
|
||||||
|
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()
|
||||||
|
|
||||||
+44
-24
@@ -1,10 +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_helpers import (add_user_to_database, change_display_name,
|
from tlapbot.owncast_helpers import (add_user_to_database, change_display_name,
|
||||||
send_chat, 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__)
|
||||||
|
|
||||||
@@ -14,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"]
|
||||||
@@ -30,26 +49,27 @@ def owncast_webhook():
|
|||||||
if data["eventData"]["user"]["authenticated"]:
|
if data["eventData"]["user"]["authenticated"]:
|
||||||
remove_duplicate_usernames(db, user_id, new_name)
|
remove_duplicate_usernames(db, user_id, new_name)
|
||||||
elif data["type"] == "CHAT":
|
elif data["type"] == "CHAT":
|
||||||
prefix = current_app.config['PREFIX']
|
if not current_app.config['PASSIVE']:
|
||||||
user_id = data["eventData"]["user"]["id"]
|
prefix = current_app.config['PREFIX']
|
||||||
display_name = data["eventData"]["user"]["displayName"]
|
user_id = data["eventData"]["user"]["id"]
|
||||||
current_app.logger.debug(f'New chat message from {display_name}:')
|
display_name = data["eventData"]["user"]["displayName"]
|
||||||
current_app.logger.debug(f'{data["eventData"]["body"]}')
|
current_app.logger.debug(f'New chat message from {display_name}:')
|
||||||
if data["eventData"]["body"].startswith(f"{prefix}help"):
|
current_app.logger.debug(f'{data["eventData"]["body"]}')
|
||||||
send_help()
|
if data["eventData"]["body"].startswith(f"{prefix}help"):
|
||||||
elif data["eventData"]["body"].startswith(f"{prefix}points"):
|
send_help()
|
||||||
points = read_users_points(db, user_id)
|
elif data["eventData"]["body"].startswith(f"{prefix}points"):
|
||||||
if points is None:
|
points = read_users_points(db, user_id)
|
||||||
send_chat("Error reading points.")
|
if points is None:
|
||||||
else:
|
send_chat("Error reading points.")
|
||||||
send_chat(f"{display_name}'s points: {points}")
|
else:
|
||||||
elif data["eventData"]["body"].startswith(f"{prefix}name_update"):
|
send_chat(f"{display_name}'s points: {points}")
|
||||||
# Forces name update in case bot didn't catch the NAME_CHANGE
|
elif data["eventData"]["body"].startswith(f"{prefix}name_update"):
|
||||||
# event. Also removes saved usernames from users with same name
|
# Forces name update in case bot didn't catch the NAME_CHANGE
|
||||||
# if user is authenticated.
|
# event. Also removes saved usernames from users with same name
|
||||||
change_display_name(db, user_id, display_name)
|
# if user is authenticated.
|
||||||
if data["eventData"]["user"]["authenticated"]:
|
change_display_name(db, user_id, display_name)
|
||||||
remove_duplicate_usernames(db, user_id, display_name)
|
if data["eventData"]["user"]["authenticated"]:
|
||||||
elif data["eventData"]["body"].startswith(prefix):
|
remove_duplicate_usernames(db, user_id, display_name)
|
||||||
handle_redeem(data["eventData"]["body"], user_id)
|
elif data["eventData"]["body"].startswith(prefix):
|
||||||
|
handle_redeem(data["eventData"]["body"], user_id)
|
||||||
return data
|
return data
|
||||||
|
|||||||
@@ -0,0 +1,227 @@
|
|||||||
|
from flask import current_app
|
||||||
|
from sqlite3 import Error
|
||||||
|
from tlapbot.owncast_helpers import use_points
|
||||||
|
|
||||||
|
def counter_exists(db, counter_name):
|
||||||
|
try:
|
||||||
|
cursor = db.execute(
|
||||||
|
"SELECT count FROM counters WHERE name = ?",
|
||||||
|
(counter_name,)
|
||||||
|
)
|
||||||
|
counter = cursor.fetchone()
|
||||||
|
if counter is None:
|
||||||
|
current_app.logger.warning("Counter not found in database.")
|
||||||
|
current_app.logger.warning("Maybe you forgot to run the refresh-counters CLI command "
|
||||||
|
"after you added a new counter to the config?")
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
except Error as e:
|
||||||
|
current_app.logger.error(f"Error occured checking if counter exists: {e.args[0]}")
|
||||||
|
current_app.logger.error(f"For counter: {counter_name}")
|
||||||
|
|
||||||
|
|
||||||
|
def add_to_counter(db, counter_name):
|
||||||
|
if counter_exists(db, counter_name):
|
||||||
|
try:
|
||||||
|
cursor = db.execute(
|
||||||
|
"UPDATE counters SET count = count + 1 WHERE name = ?",
|
||||||
|
(counter_name,)
|
||||||
|
)
|
||||||
|
db.commit()
|
||||||
|
return True
|
||||||
|
except Error as e:
|
||||||
|
current_app.logger.error(f"Error occured adding to counter: {e.args[0]}")
|
||||||
|
current_app.logger.error(f"To counter: {counter_name}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def add_to_redeem_queue(db, user_id, redeem_name, note=None):
|
||||||
|
try:
|
||||||
|
cursor = db.execute(
|
||||||
|
"INSERT INTO redeem_queue(redeem, redeemer_id, note) VALUES(?, ?, ?)",
|
||||||
|
(redeem_name, user_id, note)
|
||||||
|
)
|
||||||
|
db.commit()
|
||||||
|
return True
|
||||||
|
except Error as e:
|
||||||
|
current_app.logger.error(f"Error occured adding to redeem queue: {e.args[0]}")
|
||||||
|
current_app.logger.error(f"To user: {user_id} with redeem: {redeem_name} with note: {note}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def add_to_milestone(db, user_id, redeem_name, points_donated):
|
||||||
|
try:
|
||||||
|
cursor = db.execute(
|
||||||
|
"SELECT progress, goal FROM milestones WHERE name = ?",
|
||||||
|
(redeem_name,)
|
||||||
|
)
|
||||||
|
row = cursor.fetchone()
|
||||||
|
if row is None:
|
||||||
|
current_app.logger.warning("Milestone not found in database.")
|
||||||
|
current_app.logger.warning("Maybe you forgot to run the refresh-milestones CLI command "
|
||||||
|
"after you added a new milestone to the config?")
|
||||||
|
return False
|
||||||
|
progress, goal = row
|
||||||
|
if progress + points_donated > goal:
|
||||||
|
points_donated = goal - progress
|
||||||
|
if points_donated < 0:
|
||||||
|
points_donated = 0
|
||||||
|
if use_points(db, user_id, points_donated):
|
||||||
|
cursor = db.execute(
|
||||||
|
"UPDATE milestones SET progress = ? WHERE name = ?",
|
||||||
|
(progress + points_donated, redeem_name)
|
||||||
|
)
|
||||||
|
db.commit()
|
||||||
|
return True
|
||||||
|
except Error as e:
|
||||||
|
current_app.logger.error(f"Error occured updating milestone: {e.args[0]}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def milestone_complete(db, redeem_name):
|
||||||
|
try:
|
||||||
|
cursor = db.execute(
|
||||||
|
"SELECT complete FROM milestones WHERE name = ?",
|
||||||
|
(redeem_name,)
|
||||||
|
)
|
||||||
|
row = cursor.fetchone()
|
||||||
|
if row is None:
|
||||||
|
current_app.logger.warning("Milestone not found in database.")
|
||||||
|
current_app.logger.warning("Maybe you forgot to run the refresh-milestones CLI command "
|
||||||
|
"after you added a new milestone to the config?")
|
||||||
|
else:
|
||||||
|
return row[0]
|
||||||
|
except Error as e:
|
||||||
|
current_app.logger.error(f"Error occured checking if milestone is complete: {e.args[0]}")
|
||||||
|
|
||||||
|
|
||||||
|
def check_apply_milestone_completion(db, redeem_name):
|
||||||
|
try:
|
||||||
|
cursor = db.execute(
|
||||||
|
"SELECT progress, goal FROM milestones WHERE name = ?",
|
||||||
|
(redeem_name,)
|
||||||
|
)
|
||||||
|
row = cursor.fetchone()
|
||||||
|
if row is None:
|
||||||
|
current_app.logger.warning("Milestone not found in database.")
|
||||||
|
current_app.logger.warning("Maybe you forgot to run the refresh-milestones CLI command "
|
||||||
|
"after you added a new milestone to the config?")
|
||||||
|
else:
|
||||||
|
progress, goal = row
|
||||||
|
if progress == goal:
|
||||||
|
cursor = db.execute(
|
||||||
|
"UPDATE milestones SET complete = TRUE WHERE name = ?",
|
||||||
|
(redeem_name,)
|
||||||
|
)
|
||||||
|
db.commit()
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
except Error as e:
|
||||||
|
current_app.logger.error(f"Error occured applying milestone completion: {e.args[0]}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def all_milestones(db):
|
||||||
|
try:
|
||||||
|
cursor = db.execute(
|
||||||
|
"""SELECT name, progress, goal FROM milestones"""
|
||||||
|
)
|
||||||
|
return cursor.fetchall()
|
||||||
|
except Error as e:
|
||||||
|
current_app.logger.error(f"Error occured selecting all milestones: {e.args[0]}")
|
||||||
|
|
||||||
|
|
||||||
|
def all_counters(db):
|
||||||
|
try:
|
||||||
|
cursor = db.execute(
|
||||||
|
"""SELECT counters.name, counters.count FROM counters"""
|
||||||
|
)
|
||||||
|
return cursor.fetchall()
|
||||||
|
except Error as e:
|
||||||
|
current_app.logger.error(f"Error occured selecting all counters: {e.args[0]}")
|
||||||
|
|
||||||
|
|
||||||
|
def all_active_counters(db):
|
||||||
|
counters = all_counters(db)
|
||||||
|
all_active_counters = []
|
||||||
|
for name, count in counters:
|
||||||
|
if is_redeem_active(name):
|
||||||
|
all_active_counters.append((name, count))
|
||||||
|
return all_active_counters
|
||||||
|
|
||||||
|
|
||||||
|
def all_active_milestones(db):
|
||||||
|
milestones = all_milestones(db)
|
||||||
|
all_active_milestones = []
|
||||||
|
for name, progress, goal in milestones:
|
||||||
|
if is_redeem_active(name):
|
||||||
|
all_active_milestones.append((name, progress, goal))
|
||||||
|
return all_active_milestones
|
||||||
|
|
||||||
|
|
||||||
|
def all_active_redeems(db):
|
||||||
|
redeems = current_app.config['REDEEMS']
|
||||||
|
all_active_redeems = {}
|
||||||
|
for redeem_name, redeem_dict in redeems.items():
|
||||||
|
if redeem_dict.get('category', None):
|
||||||
|
for category in redeem_dict['category']:
|
||||||
|
if category in current_app.config['ACTIVE_CATEGORIES']:
|
||||||
|
all_active_redeems[redeem_name] = redeem_dict
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
all_active_redeems[redeem_name] = redeem_dict
|
||||||
|
return all_active_redeems
|
||||||
|
|
||||||
|
|
||||||
|
def pretty_redeem_queue(db):
|
||||||
|
try:
|
||||||
|
cursor = db.execute(
|
||||||
|
"""SELECT redeem_queue.created, redeem_queue.redeem, redeem_queue.note, points.name
|
||||||
|
FROM redeem_queue
|
||||||
|
INNER JOIN points
|
||||||
|
on redeem_queue.redeemer_id = points.id"""
|
||||||
|
)
|
||||||
|
return cursor.fetchall()
|
||||||
|
except Error as e:
|
||||||
|
current_app.logger.error(f"Error occured selecting pretty redeem queue: {e.args[0]}")
|
||||||
|
|
||||||
|
|
||||||
|
def whole_redeem_queue(db):
|
||||||
|
try:
|
||||||
|
cursor = db.execute(
|
||||||
|
"SELECT * from redeem_queue"
|
||||||
|
)
|
||||||
|
return cursor.fetchall()
|
||||||
|
except Error as e:
|
||||||
|
current_app.logger.error(f"Error occured selecting redeem queue: {e.args[0]}")
|
||||||
|
|
||||||
|
|
||||||
|
def is_redeem_active(redeem_name):
|
||||||
|
"""Checks if redeem is active. Pulls the redeem by name from config."""
|
||||||
|
active_categories = current_app.config['ACTIVE_CATEGORIES']
|
||||||
|
redeem_dict = current_app.config['REDEEMS'].get(redeem_name, None)
|
||||||
|
if redeem_dict:
|
||||||
|
if "category" in redeem_dict:
|
||||||
|
for category in redeem_dict["category"]:
|
||||||
|
if category in active_categories:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
return None # redeem does not exist, unknown active state
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def is_redeem_from_config_active(redeem, active_categories):
|
||||||
|
"""Checks if redeem is active. `redeem` is a whole key:value pair from redeems config."""
|
||||||
|
if "category" in redeem[1] and redeem[1]["category"]:
|
||||||
|
for category in redeem[1]["category"]:
|
||||||
|
if category in active_categories:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def remove_inactive_redeems(redeems, active_categories):
|
||||||
|
return dict(filter(lambda redeem: is_redeem_from_config_active(redeem, active_categories),
|
||||||
|
redeems.items()))
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
from flask import current_app
|
from flask import current_app
|
||||||
from tlapbot.db import get_db
|
from tlapbot.db import get_db
|
||||||
from tlapbot.owncast_helpers import (use_points, add_to_redeem_queue,
|
from tlapbot.owncast_requests import send_chat
|
||||||
add_to_counter, read_users_points, send_chat, remove_emoji)
|
from tlapbot.redeems import (add_to_redeem_queue, add_to_counter, add_to_milestone,
|
||||||
|
check_apply_milestone_completion, milestone_complete, is_redeem_active)
|
||||||
|
from tlapbot.owncast_helpers import use_points, read_users_points, remove_emoji
|
||||||
|
|
||||||
|
|
||||||
def handle_redeem(message, user_id):
|
def handle_redeem(message, user_id):
|
||||||
@@ -15,12 +17,34 @@ def handle_redeem(message, user_id):
|
|||||||
if redeem not in current_app.config['REDEEMS']:
|
if redeem not in current_app.config['REDEEMS']:
|
||||||
send_chat("Can't redeem, redeem not found.")
|
send_chat("Can't redeem, redeem not found.")
|
||||||
return
|
return
|
||||||
|
if not is_redeem_active(redeem):
|
||||||
|
send_chat("Can't redeem, redeem is currently not active.")
|
||||||
|
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
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
DROP TABLE IF EXISTS counters;
|
DROP TABLE IF EXISTS counters;
|
||||||
DROP TABLE IF EXISTS redeem_queue;
|
DROP TABLE IF EXISTS redeem_queue;
|
||||||
|
DROP TABLE IF EXISTS milestones;
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS points (
|
CREATE TABLE IF NOT EXISTS points (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
@@ -7,6 +8,14 @@ CREATE TABLE IF NOT EXISTS points (
|
|||||||
points INTEGER
|
points INTEGER
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE milestones (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
progress INTEGER NOT NULL,
|
||||||
|
goal INTEGER NOT NULL,
|
||||||
|
complete BOOLEAN NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
CREATE TABLE counters (
|
CREATE TABLE counters (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
@@ -20,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
|
||||||
);
|
);
|
||||||
@@ -1,19 +1,26 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<title>Redeems Dashboard</title>
|
<title>Redeems Dashboard</title>
|
||||||
</head>
|
</head>
|
||||||
<div id="script">
|
<div id="script">
|
||||||
|
|
||||||
<script src="/static/dashboard.js"></script>
|
<script src="/static/dashboard.js"></script>
|
||||||
|
|
||||||
<div class="tab">
|
<div class="tab">
|
||||||
<button class="tablinks" onclick="openTab(event, 'dashboard')", id="defaultOpen">Tlapbot dashboard</button>
|
<button class="tablinks" onclick="openTab(event, 'dashboard')" , id="defaultOpen">Tlapbot dashboard</button>
|
||||||
|
{% if not passive %}
|
||||||
|
{% if queue %}
|
||||||
|
<button class="tablinks" onclick="openTab(event, 'redeem-queue')">Redeem queue</button>
|
||||||
|
{% endif %}
|
||||||
<button class="tablinks" onclick="openTab(event, 'redeems-list')">Redeems help</button>
|
<button class="tablinks" onclick="openTab(event, 'redeems-list')">Redeems help</button>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id='dashboard' class="tabcontent">
|
<div id='dashboard' class="tabcontent">
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<h3>Redeems Dashboard</h3>
|
<h3>Redeems Dashboard</h3>
|
||||||
{% if (username and users ) %}
|
{% if (username and users ) %}
|
||||||
@@ -23,15 +30,23 @@
|
|||||||
<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 %}
|
||||||
|
|
||||||
|
{% if passive %}
|
||||||
|
<h3>Tlapbot is currently in passive mode.</h3>
|
||||||
|
<p>You can't make any redeems, but you will receive points for watching.</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if not passive %}
|
||||||
{% if counters %}
|
{% if counters %}
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
@@ -39,20 +54,49 @@
|
|||||||
<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 %}
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th colspan="2">Active milestones</th>
|
||||||
|
<th>Progress</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for milestone in milestones %}
|
||||||
|
<tr>
|
||||||
|
<td> {{ milestone[0] }} </td>
|
||||||
|
<td> <progress id="file" max={{ milestone[2] }} value={{ milestone[1] }}></progress></td>
|
||||||
|
<td> {{ milestone[1] }} / {{ milestone[2] }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</body>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id='redeem-queue' class="tabcontent">
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h3>Redeems Queue</h3>
|
||||||
|
<p>Past redeemed redeems with timestamps and notes.</p>
|
||||||
{% if queue %}
|
{% if queue %}
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Recent redeems</th>
|
<th colspan="2">Recent redeems</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Time</th>
|
<th>Time</th>
|
||||||
@@ -61,16 +105,18 @@
|
|||||||
<th>Note</th>
|
<th>Note</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
<tbody>
|
||||||
{% for row in queue %}
|
{% for row in queue %}
|
||||||
<tbody>
|
<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>
|
||||||
@@ -83,7 +129,10 @@
|
|||||||
<li><strong>Counter</strong> redeems add +1 to their counter.</li>
|
<li><strong>Counter</strong> redeems add +1 to their counter.</li>
|
||||||
<li><strong>List</strong> redeems get added to the list of recent redeems (without a note).</li>
|
<li><strong>List</strong> redeems get added to the list of recent redeems (without a note).</li>
|
||||||
<li><strong>Note</strong> redeems require you to send a message together with the redeem.</li>
|
<li><strong>Note</strong> redeems require you to send a message together with the redeem.</li>
|
||||||
|
<li><strong>Milestone</strong> redeems are long-term goals to which you can donate any amount of points you
|
||||||
|
want. They will be completed once the amount of points donated reaches the goal.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
{% if redeems %}
|
{% if redeems %}
|
||||||
<table>
|
<table>
|
||||||
@@ -95,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>
|
||||||
@@ -117,4 +172,4 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
from flask import render_template, Blueprint, request, current_app
|
from flask import render_template, Blueprint, request, current_app
|
||||||
from tlapbot.db import get_db
|
from tlapbot.db import get_db
|
||||||
from tlapbot.owncast_helpers import (pretty_redeem_queue, all_counters,
|
from tlapbot.redeems import all_active_counters, all_active_milestones, all_active_redeems, pretty_redeem_queue
|
||||||
read_all_users_with_username)
|
from tlapbot.owncast_helpers import read_all_users_with_username
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
bp = Blueprint('redeem_dashboard', __name__)
|
bp = Blueprint('redeem_dashboard', __name__)
|
||||||
@@ -18,9 +18,11 @@ def dashboard():
|
|||||||
utc_timezone = timezone.utc
|
utc_timezone = timezone.utc
|
||||||
return render_template('dashboard.html',
|
return render_template('dashboard.html',
|
||||||
queue=pretty_redeem_queue(db),
|
queue=pretty_redeem_queue(db),
|
||||||
counters=all_counters(db),
|
counters=all_active_counters(db),
|
||||||
redeems=current_app.config['REDEEMS'],
|
milestones=all_active_milestones(db),
|
||||||
|
redeems=all_active_redeems(db),
|
||||||
prefix=current_app.config['PREFIX'],
|
prefix=current_app.config['PREFIX'],
|
||||||
|
passive=current_app.config['PASSIVE'],
|
||||||
username=username,
|
username=username,
|
||||||
users=users,
|
users=users,
|
||||||
utc_timezone=utc_timezone)
|
utc_timezone=utc_timezone)
|
||||||
Reference in New Issue
Block a user