diff --git a/anonkun.py b/anonkun.py
index 6e16c7d..d358463 100644
--- a/anonkun.py
+++ b/anonkun.py
@@ -56,6 +56,7 @@ app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024
app.config['SESSION_TYPE'] = 'filesystem'
app.jinja_env.trim_blocks = True
app.jinja_env.lstrip_blocks = True
+app.jinja_env.undefined = "StrictUndefined"
Session(app)
socketio.init_app(app)
paranoid = Paranoid(app)
diff --git a/anonkun.sql b/anonkun.sql
index e5d89f9..8151b24 100644
--- a/anonkun.sql
+++ b/anonkun.sql
@@ -11,7 +11,7 @@ CREATE TABLE `quest_meta` (
`canon_title` VARCHAR(300) DEFAULT NULL,
`ident_title` VARCHAR(300) DEFAULT NULL,
`owner_id` SMALLINT UNSIGNED DEFAULT NULL,
- `dice_call` SMALLINT UNSIGNED DEFAULT NULL,
+ `open_post_id` SMALLINT UNSIGNED DEFAULT NULL,
PRIMARY KEY (`quest_id`)
) ENGINE=InnoDB CHARSET utf8mb4;
@@ -24,7 +24,7 @@ CREATE TABLE `quest_data` (
PRIMARY KEY (`post_id`)
) ENGINE=InnoDB CHARSET utf8mb4;
-CREATE TABLE `quest_dice_calls` (
+CREATE TABLE `dice_calls` (
`post_id` MEDIUMINT UNSIGNED NOT NULL,
`dice_roll` TEXT NOT NULL,
`strict` BOOLEAN DEFAULT FALSE,
@@ -33,16 +33,30 @@ CREATE TABLE `quest_dice_calls` (
PRIMARY KEY (`post_id`)
) ENGINE=InnoDB CHARSET utf8mb4;
-CREATE TABLE `quest_rolls` (
+CREATE TABLE `dice_rolls` (
`message_id` MEDIUMINT UNSIGNED NOT NULL,
`quest_id` SMALLINT UNSIGNED NOT NULL,
`post_id` MEDIUMINT UNSIGNED NOT NULL,
`roll_dice` TEXT NOT NULL,
`roll_results` TEXT NOT NULL,
- `roll_total` SMALLINT UNSIGNED NOT NULL
+ `roll_total` SMALLINT UNSIGNED NOT NULL,
PRIMARY KEY (`message_id`)
) ENGINE=InnoDB CHARSET utf8mb4;
+CREATE TABLE `polls` (
+ `post_id` MEDIUMINT UNSIGNED NOT NULL,
+ `multi_choice` BOOLEAN DEFAULT FALSE,
+ `allow_writein` BOOLEAN DEFAULT FALSE,
+ PRIMARY KEY (`post_id`)
+) ENGINE=InnoDB CHARSET utf8mb4;
+
+CREATE TABLE `poll_options` (
+ `post_id` MEDIUMINT UNSIGNED NOT NULL,
+ `quest_id` SMALLINT UNSIGNED NOT NULL,
+ `option_text` VARCHAR(200) NOT NULL,
+ `ips_voted` TEXT DEFAULT NULL
+) ENGINE=InnoDB CHARSET utf8mb4;
+
CREATE TABLE `chat_messages` (
`message_id` MEDIUMINT UNSIGNED NOT NULL AUTO_INCREMENT,
`room_id` MEDIUMINT UNSIGNED NOT NULL,
diff --git a/database.py b/database.py
index 5368ca5..adc8a81 100644
--- a/database.py
+++ b/database.py
@@ -214,58 +214,98 @@ def update_quest_post(post_id, new_post):
(new_post, post_id))
-def set_dice_call_open(post_id, quest_id, new_call=None):
- """Sets an open dice call for the given quest."""
+def set_post_open(post_id, quest_id):
+ """Sets an active post open for the given quest."""
_DB.execute(
- "UPDATE `quest_meta` SET `dice_call` = %s WHERE `quest_id` = %s",
+ "UPDATE `quest_meta` SET `open_post_id` = %s WHERE `quest_id` = %s",
(post_id, quest_id))
- if new_call:
- dice_roll, strict, dice_challence, rolls_taken = new_call
- _DB.execute(
- "INSERT INTO `quest_dice_calls`" \
- + "(`post_id`, `dice_roll`, `strict`, " \
- + "`dice_challenge`, `rolls_taken`)" \
- + "VALUES (%s, %s, %s, %s, %s)",
- (post_id, dice_roll, strict, dice_challence, rolls_taken))
-
-def set_dice_call_closed(quest_id):
+def set_post_closed(quest_id):
"""Closes a quest's dice call."""
_DB.execute(
- "UPDATE `quest_meta` SET `dice_call` = NULL WHERE `quest_id` = %s",
+ "UPDATE `quest_meta` SET `open_post_id` = NULL WHERE `quest_id` = %s",
(quest_id,))
+def insert_dice_call(post_id, quest_id, new_call):
+ """Inserts a new dice call."""
+ dice_roll, strict, dice_challence, rolls_taken = new_call
+ _DB.execute(
+ "INSERT INTO `dice_calls`" \
+ + "(`post_id`, `dice_roll`, `strict`,`dice_challenge`,`rolls_taken`)" \
+ + "VALUES (%s, %s, %s, %s, %s)",
+ (post_id, dice_roll, strict, dice_challence, rolls_taken))
+
+
def get_dice_call(post_id):
"""Retrives the currently open dice call, if there is one."""
data = _DB.execute(
- "SELECT * FROM `quest_dice_calls` WHERE `post_id` = %s",
+ "SELECT * FROM `dice_calls` WHERE `post_id` = %s",
(post_id,)).fetchone()
return data
def insert_quest_roll(message_id, quest_id, post_id, roll_data):
- """Inserts a user roll into the `quest_rolls` table."""
+ """Inserts a user roll into the `dice_rolls` table."""
ins = (message_id, quest_id, post_id) + roll_data
_DB.execute(
- "INSERT INTO `quest_rolls`" \
+ "INSERT INTO `dice_rolls`" \
+ "(`message_id`, `quest_id`, `post_id`, " \
+ "`roll_dice`, `roll_results`, `roll_total`)" \
+ "VALUES (%s, %s, %s, %s, %s, %s)",
(ins))
-def get_quest_rolls(quest_id=None, post_id=None):
+def get_dice_rolls(quest_id=None, post_id=None):
"""Gets all rolls for the given quest."""
if quest_id:
- sql = "SELECT * FROM `quest_rolls` WHERE `quest_id` = %s "
+ sql = "SELECT * FROM `dice_rolls` WHERE `quest_id` = %s "
ins = quest_id
elif post_id:
- sql = "SELECT * FROM `quest_rolls` WHERE `post_id` = %s "
+ sql = "SELECT * FROM `dice_rolls` WHERE `post_id` = %s "
ins = post_id
else:
return
sql += "ORDER BY `message_id` ASC"
data = _DB.execute(sql, (ins,)).fetchall()
return data
+
+
+def insert_poll(post_id, multi_choice, allow_writein):
+ """Inserts a new poll post."""
+ _DB.execute(
+ "INSERT INTO `polls` " \
+ + "(`post_id`, `multi_choice`, `allow_writein`) " \
+ + "VALUES (%s, %s, %s)",
+ (post_id, multi_choice, allow_writein))
+
+
+def get_poll(post_id):
+ """Gets poll information."""
+ data = _DB.excute(
+ "SELECT * FROM `polls` WHERE `post_id` = %s", (post_id,))
+ return data
+
+
+def insert_poll_option(post_id, quest_id, option_text):
+ """Insert a new poll option. ips_voted will be NULL to start."""
+ _DB.execute(
+ "INSERT INTO `poll_options` " \
+ + "(`post_id`, `quest_id`, `option_text`) VALUES (%s, %s, %s)",
+ (post_id, quest_id, option_text))
+
+
+def get_poll_options(quest_id=None, post_id=None):
+ """Gets all relevent poll options for a given quest or post."""
+ sql = "SELECT * FROM `poll_options` WHERE "
+ if quest_id:
+ sql += "`quest_id` = %s"
+ ins = (quest_id,)
+ elif post_id:
+ sql += "`post_id` = %s"
+ ins = (post_id,)
+ else:
+ return
+ data = _DB.execute(sql, ins)
+ return data
diff --git a/events.py b/events.py
index 2fb7231..755ea5e 100644
--- a/events.py
+++ b/events.py
@@ -2,9 +2,11 @@
"""
SocketIO events.
"""
+# TODO: harden the fuck up
import re
import time
import random
+import functools
import bleach
from flask import session
@@ -15,6 +17,28 @@ import database as db
socketio = SocketIO()
+def qm_only(msg=""):
+ """
+ A decorator function to protect certain endpoints so that only the
+ QM can access them.
+ """
+ # TODO: better docstring, test this more thoroughly
+ def actual_decorator(func):
+ @functools.wraps(func)
+ def _nop(*args, **kwargs):
+ data = args[0]
+ room = data.get("room")
+ res = db.get_quest_meta(quest_id=room)
+ if not res:
+ return msg
+ if session.get("user_id") != res[3]:
+ return msg
+ return func(*args, **kwargs)
+ return _nop
+ return actual_decorator
+
+
+
@socketio.on('joined')
def joined(data):
"""
@@ -76,7 +100,7 @@ def message(data):
roll_data = (dice_roll, roll_results, roll_total)
db.insert_quest_roll(message_id, room, dice_call_id, roll_data)
- if len(db.get_quest_rolls(post_id=dice_call_id)) == dice_call[4]:
+ if len(db.get_dice_rolls(post_id=dice_call_id)) == dice_call[4]:
db.set_dice_call_closed(room)
emit("close_dice_call", {"post_id": dice_call_id}, room=room)
@@ -123,46 +147,38 @@ def handle_dice(data):
@socketio.on("new_post")
+@qm_only()
def new_post(data, internal=False):
"""
Called when the QM makes a new post.
"""
- room = data["room"]
- res = db.get_quest_meta(quest_id=room)
- if not res:
- return
- if session.get("user_id") != res[3]:
- return
-
- post = data["post"]
+ room = data.get("room")
+ post = data.get("post")
post = bleach.clean(post.strip())
post = post.replace("\n", "
")
post = tools.handle_img(post)
+ date = int(time.time())
+
+ post_id = db.insert_quest_post(room, "text", post, date)
+ db.set_post_closed(room)
data = {}
data["post"] = [post]
data["post_type"] = "text"
- data["date"] = int(time.time())
- post_id = db.insert_quest_post(room, "text", post, data["date"])
- db.set_dice_call_closed(room);
+ data["date"] = date
data["post_id"] = post_id
emit("new_post", data, room=room)
@socketio.on("update_post")
+@qm_only()
def update_post(data):
"""
Called when the QM edits and saves a post.
"""
- room = data["room"]
- res = db.get_quest_meta(quest_id=room)
- if not res:
- return
- if session.get("user_id") != res[3]:
- return
-
# TODO: enforce only update text posts
+ room = data["room"]
post = data["post"]
post = post.strip().replace("
", "
")
post = tools.handle_img(post)
@@ -178,30 +194,25 @@ def update_post(data):
@socketio.on("dice_post")
+@qm_only()
def dice_post(data):
"""
Called when the QM posts a new dice call.
"""
room = data["room"]
- res = db.get_quest_meta(quest_id=room)
- if not res:
- return
- if session.get("user_id") != res[3]:
- return
-
data = {k: v for k, v in data.items() if v}
try:
diceNum = int(data.get("diceNum", 0))
diceSides = int(data.get("diceSides", 0))
diceMod = int(data.get("diceMod", 0))
diceChal = int(data.get("diceChal", 0))
- diceRolls = int(data.get("diceRolls", 0))
+ diceRollsTaken = int(data.get("diceRollsTaken", 0))
assert 0 < diceNum < 100
assert 0 < diceSides < 100
assert -1000 < diceMod < 1000
assert 0 <= diceChal < 100
- assert 0 <= diceRolls < 100
+ assert 0 <= diceRollsTaken < 100
except (ValueError, AssertionError):
return
diceStrict = bool(data.get("diceStrict"))
@@ -215,56 +226,83 @@ def dice_post(data):
post = "Roll " + dice_roll
if diceChal:
post += " vs DC" + str(diceChal)
+ date = int(time.time())
+
+ post_id = db.insert_quest_post(room, "dice", post, date)
+ new_call = (dice_roll, diceStrict, diceChal, diceRollsTaken)
+ db.insert_dice_call(post_id, room, new_call)
+ db.set_post_open(post_id, room)
data = {}
data["post"] = post
data["post_type"] = "dice"
- data["date"] = int(time.time())
- post_id = db.insert_quest_post(room, "dice", post, data["date"])
+ data["date"] = date
data["post_id"] = post_id
-
- new_call = (dice_roll, diceStrict, diceChal, diceRolls)
- db.set_dice_call_open(post_id, room, new_call)
emit("new_post", data, room=room)
-@socketio.on('close_dice_call')
+@socketio.on('close_post')
+@qm_only()
def close_dice_call(data):
"""
- Closes an active dice call.
+ Closes an active post.
"""
room = data["room"]
- res = db.get_quest_meta(quest_id=room)
- if not res:
- return
- if session.get("user_id") != res[3]:
- return
-
post_id = data.get("post_id")
- db.set_dice_call_closed(room)
+ db.set_post_closed(room)
data = {}
data["post_id"] = post_id
- emit("close_dice_call", data, room=room)
+ emit("close_post", data, room=room)
-@socketio.on("open_dice_call")
+@socketio.on("open_post")
+@qm_only()
def open_dice_call(data):
"""
- Opens a closed dice call. This is only permitted if the dice call is
+ Opens an active post. This is only permitted if the active post is
the last post in the quest.
"""
- room = data["room"]
- res = db.get_quest_meta(quest_id=room)
- if not res:
- return
- if session.get("user_id") != res[3]:
- return
-
# TODO: enforce only open if last post
+ room = data["room"]
post_id = data.get("post_id")
- db.set_dice_call_open(post_id, room)
+ db.set_post_open(post_id, room)
data = {}
data["post_id"] = post_id
- emit("open_dice_call", data, room=room)
+ emit("open_post", data, room=room)
+
+
+@socketio.on("poll_post")
+@qm_only()
+def poll_post(data):
+ """
+ Called when the QM posts a new dice call.
+ """
+ room = data.pop("room", None)
+ multi_choice = bool(data.pop("pollAllowMultipleChoices", None))
+ allow_writein = bool(data.pop("pollAllowUserOptions", None))
+ options = []
+ for key, value in data.items():
+ if not value or not key.startswith("pollOption-"):
+ continue
+ if len(value) >= 200:
+ return
+ options.append(value)
+
+ post = "Poll"
+ date = int(time.time())
+
+ post_id = db.insert_quest_post(room, "poll", post, date)
+ db.insert_poll(post_id, multi_choice, allow_writein)
+ for option in options:
+ db.insert_poll_option(post_id, room, option)
+ db.set_post_open(post_id, room)
+
+ data = {}
+ data["post"] = post
+ data["post_id"] = post_id
+ data["post_type"] = "poll"
+ data["date"] = int(time.time())
+ data["options"] = options
+ emit("new_post", data, room=room)
diff --git a/templates/quest.html b/templates/quest.html
index fefb014..d2209ad 100644
--- a/templates/quest.html
+++ b/templates/quest.html
@@ -31,15 +31,17 @@
post_str += 'textPost">';
} else if (data.post_type == 'dice') {
post_str += 'dicePost active_post">';
+ } else if (data.post_type == 'poll') {
+ post_str += 'pollPost active_post">';
}
post_str += '
' + strftime(data.date);
{% if session.get("user_id") == owner_id %}
if (data.post_type == 'text') {
post_str += '
Edit';
post_str += '
Save';
- } else if (data.post_type == 'dice') {
- post_str += '
Close';
- post_str += '
Open'
+ } else if (data.post_type == 'dice' || data.post_type == 'poll') {
+ post_str += '
Close';
+ post_str += '
Open'
}
{% endif %}
post_str += '
';
@@ -47,6 +49,13 @@
post_str += data.post;
} else if (data.post_type == 'dice') {
post_str += '
' + data.post + ' - Open
';
+ } else if (data.post_type == 'poll') {
+ post_str += '
' + data.post + ' - Open
';
+ post_str += '
';
+ for (i = 0; i < data.options.length; i++) {
+ post_str += '' + data.options[i] + ' |
';
+ }
+ post_str += '
';
}
post_str += '
';
qposts.innerHTML = qposts.innerHTML + post_str;
@@ -61,17 +70,17 @@
post.innerHTML += '' + data.post + '
';
}
});
- socket.on('close_dice_call', function(data) {
+ socket.on('close_post', function(data) {
let post = document.getElementById('questPostData-' + data.post_id);
post.children[0].textContent = post.children[0].textContent.replace('Open', 'Closed');
- document.getElementById('close_dc-' + data.post_id).style.display = 'none';
- document.getElementById('open_dc-' + data.post_id).style.display = 'initial';
+ document.getElementById('close_post_id-' + data.post_id).style.display = 'none';
+ document.getElementById('open_post_id-' + data.post_id).style.display = 'initial';
});
- socket.on('open_dice_call', function(data) {
+ socket.on('open_post', function(data) {
let post = document.getElementById('questPostData-' + data.post_id);
post.firstElementChild.textContent = post.firstElementChild.textContent.replace('Closed', 'Open');
- document.getElementById('close_dc-' + data.post_id).style.display = 'initial';
- document.getElementById('open_dc-' + data.post_id).style.display = 'none';
+ document.getElementById('close_post_id-' + data.post_id).style.display = 'initial';
+ document.getElementById('open_post_id-' + data.post_id).style.display = 'none';
});
let mtarea = document.getElementById('messageTextArea');
mtarea.addEventListener('keypress', function(event) {
@@ -120,32 +129,30 @@
document.getElementById('savePost-' + post_id).style.display = 'none';
document.getElementById('editPost-' + post_id).style.display = 'initial';
}
- function dice_post() {
- let formData = new FormData(document.getElementById('QMDicePostForm'));
+ function form_post(form_id, emit_msg) {
+ let formData = new FormData(document.getElementById(form_id));
let obj = {};
formData.forEach(function(value, key) {
obj[key] = value;
});
obj.room = {{ room_id }};
- socket.emit('dice_post', obj);
- document.getElementById('QMDicePostForm').reset();
+ socket.emit(emit_msg, obj);
+ document.getElementById(form_id).reset();
}
- function close_dice_call(post_id) {
- socket.emit('close_dice_call', {post_id: post_id, room: {{ room_id }}});
+ function close_post(post_id) {
+ socket.emit('close_post', {post_id: post_id, room: {{ room_id }}});
}
- function open_dice_call(post_id) {
- socket.emit('open_dice_call', {post_id: post_id, room: {{ room_id }}});
+ function open_post(post_id) {
+ socket.emit('open_post', {post_id: post_id, room: {{ room_id }}});
}
function deactivate_post() {
let post = document.getElementsByClassName('active_post');
if (post.length == 0) { return; }
post = post[0];
- if (post.classList.contains('dicePost')) {
- post.children[1].children[0].textContent = post.children[1].children[0].textContent.replace('Open', 'Closed');
- post.classList.remove("active_post");
- post.children[0].children[2].outerHTML = "";
- post.children[0].children[1].outerHTML = "";
- }
+ post.children[1].children[0].textContent = post.children[1].children[0].textContent.replace('Open', 'Closed');
+ post.classList.remove("active_post");
+ post.children[0].children[2].outerHTML = "";
+ post.children[0].children[1].outerHTML = "";
}
function insertPollOption() {
let opts = document.getElementById('pollOptions');
@@ -195,6 +202,8 @@
{% elif quest_post[2] == "dice" %}
+ {% elif quest_post[2] == "poll" %}
+
{% endif %}
{{ quest_post[4] | strftime }}
@@ -202,13 +211,13 @@
{% if quest_post[2] == "text" %}
Edit
Save
- {% elif quest_post[2] == "dice" and quest_post == quest_posts|last %}
- {% if quest_post[0] == dice_call %}
-
Close
-
Open
+ {% elif quest_post[2] == "dice" or quest_post[2] == "poll" and quest_post == quest_posts|last %}
+ {% if quest_post[0] == open_post_id %}
+
Close
+
Open
{% else %}
-
Close
-
Open
+
Close
+
Open
{% endif %}
{% endif %}
{% endif %}
@@ -219,14 +228,25 @@
{{ quest_post[3] }}
{% endautoescape %}
{% elif quest_post[2] == "dice" %}
-
{{ quest_post[3] }} - {% if quest_post[0] == dice_call_id %}Open{% else %}Closed{% endif %}
- {% for quest_roll in quest_rolls %}
- {% if quest_roll[2] == quest_post[0] %}
-
-
Rolled {{ quest_roll[4] }} = {{ quest_roll[5] }} ({{ quest_roll[3] }}){% if quest_post[0]|dice_chal != 0 %} - {% if quest_roll[5] >= quest_post[0]|dice_chal %}Pass{% else %} Fail{% endif %}{% endif %}
+
{{ quest_post[3] }} - {% if quest_post[0] == open_post_id %}Open{% else %}Closed{% endif %}
+ {% for dice_roll in dice_rolls %}
+ {% if dice_roll[2] == quest_post[0] %}
+
+ Rolled {{ dice_roll[4] }} = {{ dice_roll[5] }} ({{ dice_roll[3] }}){% if quest_post[0]|dice_chal != 0 %} - {% if dice_roll[5] >= quest_post[0]|dice_chal %}Pass{% else %} Fail{% endif %}{% endif %}
{% endif %}
{% endfor %}
+ {% elif quest_post[2] == "poll" %}
+
{{ quest_post[3] }} - {% if quest_post[0] == open_post_id %}Open{% else %}Closed{% endif %}
+
+ {% for option in poll_options %}
+ {% if option[0] == quest_post[0] %}
+
+ {{ option[2] }} |
+
+ {% endif %}
+ {% endfor %}
+
{% endif %}
@@ -247,7 +267,7 @@
Dice for the dice god.
-