Compare commits
No commits in common. "acbc93beddb59e2ad6bc04eb0a51055bd02e34fe" and "31426fd85139f0d6d8c23e35caa3ee0a4a294987" have entirely different histories.
acbc93bedd
...
31426fd851
13
README.md
13
README.md
|
@ -323,21 +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": {"goal": 1000, "type": "milestone", "info": "Streamer will go nap when the goal is reached."},
|
"go_nap": {"type": "milestone", "info": "Streamer will go nap when the goal is reached.", "goal": 1000},
|
||||||
"inactive": {"price": 100, "type": "note", "info": "Example redeem that is inactive by default", "category": ["inactive"]}
|
"inactive": {"price": 100, "type": "note", "info": "Example redeem that is inactive by default", "category": ["inactive"]}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
#### File format
|
#### File format
|
||||||
`redeems.py` is a config file with just a `REDEEMS` key, that assigns a dictionary of redeems to it.
|
`redeems.py` is a config file with just a `REDEEMS` key, that assigns a dictionary of redeems to it.
|
||||||
Each dictionary entry is a redeem, and the dictionary keys are strings that decides the chat command for the redeem.
|
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
|
The value is another dictionary that needs to have entries for `"type"`,
|
||||||
an entry for `"price"` for non-milestones or `"goal"` for milestones.
|
an entry for `"price"` unless the redeem is a milestone,
|
||||||
Optionally, each redeem can also have `"info"` and `"category"` entries.
|
and optionally `"info"` and `"category"`.
|
||||||
|
If the `"type"` is `"milestone"`, there's an additional required `"goal"` field as well.
|
||||||
|
|
||||||
- `"price"` value should be an integer that decides how many points the redeem will cost. Milestone redeems don't use the `"price"` value, they instead need to have a `"goal"`.
|
- `"price"` value should be an integer that decides how many points the redeem will cost. Milestone redeems don't use the `"price"` value.
|
||||||
- `"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.
|
- `"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).
|
- `"info"` value should be a string that describes what the command does. It's optional, but I recommend writing one for all `"list"`, `"note"` and `"milestone"` redeems (so that chatters know what they're redeeming and whether they should leave a note).
|
||||||
|
- `"goal"` is a required field for milestone goals. It should be an integer, deciding the amount of points required to complete the milestone.
|
||||||
- `"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,12 +2,9 @@ import os
|
||||||
import logging
|
import logging
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
from apscheduler.schedulers.background import BackgroundScheduler
|
from apscheduler.schedulers.background import BackgroundScheduler
|
||||||
from datetime import datetime
|
|
||||||
from tlapbot.db import get_db
|
from tlapbot.db import get_db
|
||||||
from tlapbot.owncast_requests import is_stream_live, give_points_to_chat
|
from tlapbot.owncast_requests import is_stream_live, give_points_to_chat
|
||||||
from tlapbot.redeems import remove_inactive_redeems
|
from tlapbot.redeems import remove_inactive_redeems
|
||||||
from tlapbot.helpers import (get_last_online_time, delete_last_online_time,
|
|
||||||
save_last_online_time)
|
|
||||||
|
|
||||||
def create_app(test_config=None):
|
def create_app(test_config=None):
|
||||||
app = Flask(__name__, instance_relative_config=True)
|
app = Flask(__name__, instance_relative_config=True)
|
||||||
|
@ -59,14 +56,9 @@ def create_app(test_config=None):
|
||||||
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
|
||||||
|
|
|
@ -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": {"goal": 1000, "type": "milestone", "info": "Streamer will go nap when the goal is reached."},
|
"go_nap": {"type": "milestone", "info": "Streamer will go nap when the goal is reached.", "goal": 1000},
|
||||||
"inactive": {"price": 100, "type": "note", "info": "Example redeem that is inactive by default", "category": ["inactive"]}
|
"inactive": {"price": 100, "type": "note", "info": "Example redeem that is inactive by default", "category": ["inactive"]}
|
||||||
}
|
}
|
|
@ -95,37 +95,6 @@ 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(
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
from flask import Flask, request, json, Blueprint, current_app
|
from flask import Flask, request, json, Blueprint, current_app
|
||||||
from datetime import datetime
|
from tlapbot.db import get_db
|
||||||
from tlapbot.db import get_db, refresh_counters, clear_redeem_queue
|
|
||||||
from tlapbot.owncast_requests import send_chat
|
from tlapbot.owncast_requests import send_chat
|
||||||
from tlapbot.owncast_helpers import (add_user_to_database, change_display_name,
|
from tlapbot.owncast_helpers import (add_user_to_database, change_display_name,
|
||||||
read_users_points, remove_duplicate_usernames, get_last_online_time, delete_last_online_time)
|
read_users_points, remove_duplicate_usernames)
|
||||||
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__)
|
||||||
|
|
||||||
|
@ -17,22 +15,6 @@ 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"]
|
||||||
|
|
|
@ -29,9 +29,4 @@ 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
|
|
||||||
);
|
);
|
|
@ -30,14 +30,12 @@
|
||||||
<th>Your points balance</th>
|
<th>Your points balance</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
|
||||||
{% for user in users %}
|
{% for user in users %}
|
||||||
<tr>
|
<tbody>
|
||||||
<td> {{ user[0] }} </td>
|
<td> {{ user[0] }} </td>
|
||||||
<td> {{ user[1] }} </td>
|
<td> {{ user[1] }} </td>
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
</tbody>
|
||||||
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
@ -54,14 +52,12 @@
|
||||||
<th>Active counters</th>
|
<th>Active counters</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
|
||||||
{% for counter in counters %}
|
{% for counter in counters %}
|
||||||
<tr>
|
<tbody>
|
||||||
<td> {{ counter[0] }} </td>
|
<td> {{ counter[0] }} </td>
|
||||||
<td> {{ counter[1] }} </td>
|
<td> {{ counter[1] }} </td>
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
</tbody>
|
||||||
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if milestones %}
|
{% if milestones %}
|
||||||
|
@ -72,15 +68,13 @@
|
||||||
<th>Progress</th>
|
<th>Progress</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
|
||||||
{% for milestone in milestones %}
|
{% for milestone in milestones %}
|
||||||
<tr>
|
<tbody>
|
||||||
<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>
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
</tbody>
|
||||||
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -105,18 +99,16 @@
|
||||||
<th>Note</th>
|
<th>Note</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
|
||||||
{% for row in queue %}
|
{% for row in queue %}
|
||||||
<tr>
|
<tbody>
|
||||||
<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 %}
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
</tbody>
|
||||||
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</body>
|
</body>
|
||||||
|
@ -144,22 +136,20 @@
|
||||||
<th>Description</th>
|
<th>Description</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
|
||||||
{% for redeem, redeem_info in redeems.items() %}
|
{% for redeem, redeem_info in redeems.items() %}
|
||||||
<tr>
|
<tbody>
|
||||||
<td>{{ prefix }}{{ redeem }}</td>
|
<td>{{ prefix }}{{ redeem }}</td>
|
||||||
{% if redeem_info["type"] == "milestone" %}
|
{% if redeem_info["type"] == "milestone" %}
|
||||||
<td></td>
|
<td></td>
|
||||||
{% else %}
|
{% else %}
|
||||||
<td>{{ redeem_info["price"] }}</td>
|
<td>{{ redeem_info["price"] }}</td>
|
||||||
{% endif %}
|
{% 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 %}
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
</tbody>
|
||||||
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</body>
|
</body>
|
||||||
|
|
Loading…
Reference in New Issue