fc68950ade
Orgmembers will nolonger get invited into the priv channel of their orgbot.. (on reboot only) Added Nickname to !history (only useful for !history char_id>)
247 lines
11 KiB
Python
247 lines
11 KiB
Python
import time
|
|
|
|
from core.chat_blob import ChatBlob
|
|
from core.command_param_types import Const, Any, Int
|
|
from core.decorators import instance, command, event
|
|
from modules.core.accounting.services.account_service import AccountService
|
|
|
|
|
|
@instance()
|
|
class PollController:
|
|
def inject(self, registry):
|
|
self.bot = registry.get_instance("bot")
|
|
self.db = registry.get_instance("db")
|
|
self.util = registry.get_instance("util")
|
|
self.text = registry.get_instance("text")
|
|
self.job_scheduler = registry.get_instance("job_scheduler")
|
|
self.pork_service = registry.get_instance("pork_service")
|
|
self.command_alias_service = registry.get_instance("command_alias_service")
|
|
self.account_service: AccountService = registry.get_instance("account_service")
|
|
|
|
def start(self):
|
|
self.db.exec("CREATE TABLE IF NOT EXISTS poll ("
|
|
"id INT PRIMARY KEY AUTO_INCREMENT, "
|
|
"question VARCHAR(1024) NOT NULL, "
|
|
"duration INT NOT NULL, "
|
|
"min_access_level VARCHAR(20) NOT NULL, "
|
|
"char_id INT NOT NULL, "
|
|
"created_at INT NOT NULL, "
|
|
"finished_at INT NOT NULL, "
|
|
"is_finished SMALLINT NOT NULL)")
|
|
self.db.exec("CREATE TABLE IF NOT EXISTS poll_choice ("
|
|
"id INT PRIMARY KEY AUTO_INCREMENT, "
|
|
"poll_id INT NOT NULL, "
|
|
"choice VARCHAR(1024))")
|
|
self.db.exec("CREATE TABLE IF NOT EXISTS poll_vote ("
|
|
"poll_id INT NOT NULL, "
|
|
"choice_id INT NOT NULL, "
|
|
"char_id INT NOT NULL)")
|
|
|
|
self.command_alias_service.add_alias("vote", "poll")
|
|
|
|
@command(command="poll", params=[], access_level="member",
|
|
description="List the polls")
|
|
def poll_list_cmd(self, request):
|
|
blob = ""
|
|
t = int(time.time())
|
|
state = ""
|
|
polls = self.get_polls()
|
|
for poll in polls:
|
|
if poll.finished_at > t and state != "running":
|
|
state = "running"
|
|
blob += "\n<header2>Running</header2>\n"
|
|
elif poll.finished_at <= t and state != "finished":
|
|
state = "finished"
|
|
blob += "\n<header2>Finished</header2>\n"
|
|
|
|
if state == "running":
|
|
time_string = self.util.time_to_readable(poll.finished_at - t) + " left"
|
|
else:
|
|
time_string = self.util.time_to_readable(t - poll.finished_at) + " ago"
|
|
|
|
blob += f"{poll.id:d}. {self.text.make_tellcmd(poll.question, f'poll {poll.id:d}')} " \
|
|
f"({poll.total_cnt:d}) - {time_string}\n"
|
|
|
|
return ChatBlob(f"Polls ({len(polls):d})", blob)
|
|
|
|
@command(command="poll", params=[Int("poll_id")], access_level="member",
|
|
description="View a poll")
|
|
def poll_view_cmd(self, request, poll_id):
|
|
poll = self.get_poll(poll_id)
|
|
|
|
if not poll:
|
|
return f"Could not find poll with ID <highlight>{poll_id:d}</highlight>."
|
|
|
|
return self.show_poll_details_blob(poll)
|
|
|
|
@command(command="poll",
|
|
params=[Const("add"), Any("duration|poll_question|option1|option2|...")],
|
|
access_level="moderator",
|
|
description="Add a poll", sub_command="add")
|
|
def poll_add_cmd(self, request, _, options):
|
|
options = options.split("|")
|
|
|
|
if len(options) < 4:
|
|
return "You must enter a duration, a poll question, and at least two choices."
|
|
|
|
time_str = options.pop(0).strip()
|
|
question = options.pop(0).strip()
|
|
choices = options
|
|
|
|
duration = self.util.parse_time(time_str)
|
|
if duration == 0:
|
|
return "You must enter a valid duration."
|
|
|
|
poll_id = self.add_poll(question, request.sender.char_id, duration)
|
|
for choice in choices:
|
|
self.add_poll_choice(poll_id, choice.strip())
|
|
|
|
self.create_scheduled_job(self.get_poll(poll_id))
|
|
|
|
return self.show_poll_details_blob(self.get_poll(poll_id))
|
|
|
|
@command(command="poll", params=[Int("poll_id"), Const("vote"), Int("choice_id")], access_level="member",
|
|
description="Vote on a poll")
|
|
def poll_vote_cmd(self, request, poll_id, _, choice_id):
|
|
poll = self.get_poll(poll_id)
|
|
if not poll:
|
|
return f"Could not find poll with id <highlight>{poll_id:d}</highlight>."
|
|
if poll.is_finished == 1:
|
|
return 'The poll has ended already, your vote has <highlight>not</highlight> been registered.'
|
|
choice = self.db.query_single("SELECT * FROM poll_choice WHERE poll_id = ? AND id = ?", [poll_id, choice_id])
|
|
if not choice:
|
|
return f"Could not find choice with id <highlight>{choice_id:d}</highlight> " \
|
|
f"for poll id <highlight>{poll_id:d}</highlight>."
|
|
|
|
main = self.account_service.get_main(request.sender.char_id)
|
|
|
|
# retrieve pork info
|
|
self.pork_service.get_character_info(main.char_id)
|
|
|
|
cnt = self.db.exec("DELETE FROM poll_vote "
|
|
"WHERE poll_id = ? "
|
|
"AND (char_id = ? OR char_id = ?)",
|
|
[poll_id, main.char_id, request.sender.char_id])
|
|
self.db.exec("INSERT INTO poll_vote (poll_id, choice_id, char_id) "
|
|
"VALUES (?, ?, ?)",
|
|
[poll_id, choice_id, main.char_id])
|
|
|
|
if cnt > 0:
|
|
return "Your vote has been updated."
|
|
else:
|
|
return "Your vote has been saved."
|
|
|
|
@command(command="poll", params=[Int("poll_id"), Const("remvote")], access_level="member",
|
|
description="Remove your vote on a poll")
|
|
def poll_remvote_cmd(self, request, poll_id, _):
|
|
poll = self.get_poll(poll_id)
|
|
if not poll:
|
|
return f"Could not find poll with id <highlight>{poll_id:d}</highlight>."
|
|
if poll.is_finished == 1:
|
|
return "The poll has ended already you may not remove your vote."
|
|
main = self.account_service.get_main(request.sender.char_id)
|
|
|
|
cnt = self.db.exec("DELETE FROM poll_vote "
|
|
"WHERE poll_id = ? "
|
|
"AND (char_id = ? OR char_id = ?)",
|
|
[poll_id, main.char_id, request.sender.char_id])
|
|
if cnt > 0:
|
|
return "Your vote has been removed."
|
|
else:
|
|
return "You have not voted for that choice."
|
|
|
|
@event(event_type="connect", description="Check for finished polls", is_hidden=True)
|
|
def connect_event(self, event_type, event_data):
|
|
self.check_for_finished_polls()
|
|
self.create_scheduled_jobs_for_polls()
|
|
|
|
@event(event_type="member_logon",
|
|
description="Send active polls to org members logging on")
|
|
def poll_on_logon(self, event_type, event_data):
|
|
if self.bot.is_ready():
|
|
# Only proceed if the "normal" checks are fine
|
|
if not self.account_service.simple_checks(event_data.account):
|
|
return
|
|
if event_data.account.news_spam == 1:
|
|
data = self.db.query("SELECT * FROM poll WHERE is_finished != 1 AND "
|
|
"id NOT IN (SELECT poll_id FROM poll_vote WHERE char_id = ?) "
|
|
"ORDER BY finished_at, id", [event_data.account.char_id])
|
|
if data:
|
|
row = data[0]
|
|
self.bot.send_private_message(event_data.packet.char_id, self.show_poll_details_blob(row))
|
|
|
|
def create_scheduled_jobs_for_polls(self):
|
|
data = self.db.query("SELECT * FROM poll WHERE is_finished != 1")
|
|
|
|
for row in data:
|
|
self.create_scheduled_job(row)
|
|
|
|
def check_for_finished_polls(self):
|
|
data = self.db.query("SELECT * FROM poll WHERE is_finished = 0 AND finished_at <= ?", [int(time.time())])
|
|
|
|
for row in data:
|
|
self.end_poll(row)
|
|
|
|
def show_poll_details_blob(self, poll):
|
|
blob = ""
|
|
blob += f"Duration: <highlight>{self.util.time_to_readable(poll.duration)}</highlight>\n"
|
|
blob += f"Created: <highlight>{self.util.format_datetime(poll.created_at)}</highlight>\n"
|
|
blob += f"Finished: <highlight>{self.util.format_datetime(poll.finished_at)}</highlight>\n"
|
|
|
|
blob += "\n<header2>Choices</header2>\n"
|
|
idx = 1
|
|
for choice in self.get_choices(poll.id):
|
|
blob += f"{idx:d}. {self.text.make_tellcmd(choice.choice, f'poll {poll.id:d} vote {choice.id:d}')} " \
|
|
f"({choice.cnt:d})\n"
|
|
for vote in self.get_votes(choice.id):
|
|
blob += f"<tab>{self.text.format_char_info(vote)}\n"
|
|
idx += 1
|
|
|
|
return ChatBlob(f"Poll ID {poll.id:d}: {poll.question}", blob)
|
|
|
|
def get_polls(self):
|
|
return self.db.query("SELECT p.*, "
|
|
"(SELECT COUNT(1) FROM poll_vote v WHERE v.poll_id = p.id) AS total_cnt "
|
|
"FROM poll p "
|
|
"ORDER BY finished_at DESC")
|
|
|
|
def get_poll(self, poll_id):
|
|
return self.db.query_single("SELECT * FROM poll WHERE id = ?", [poll_id])
|
|
|
|
def get_choices(self, poll_id):
|
|
return self.db.query("SELECT c.id, c.choice, COUNT(v.char_id) AS cnt FROM poll_choice c "
|
|
"LEFT JOIN poll_vote v ON c.id = v.choice_id "
|
|
"WHERE c.poll_id = ? "
|
|
"GROUP BY c.id, c.choice "
|
|
"ORDER BY c.id", [poll_id])
|
|
|
|
def get_votes(self, choice_id):
|
|
return self.db.query("SELECT p.* FROM poll_vote v "
|
|
"LEFT JOIN player p ON v.char_id = p.char_id "
|
|
"WHERE v.choice_id = ?", [choice_id])
|
|
|
|
def add_poll(self, question, char_id, duration, min_access_level="member"):
|
|
t = int(time.time())
|
|
self.db.exec("INSERT INTO poll (question, duration, min_access_level, "
|
|
"char_id, created_at, finished_at, is_finished) "
|
|
"VALUES (?, ?, ?, ?, ?, ?, ?)",
|
|
[question, duration, min_access_level, char_id, t, t + duration, 0])
|
|
|
|
return self.db.last_insert_id()
|
|
|
|
def add_poll_choice(self, poll_id, choice):
|
|
self.db.exec("INSERT INTO poll_choice (poll_id, choice) VALUES (?, ?)", [poll_id, choice])
|
|
|
|
return self.db.last_insert_id()
|
|
|
|
def create_scheduled_job(self, poll):
|
|
self.job_scheduler.scheduled_job(self.show_results, poll.finished_at, poll.id)
|
|
|
|
def show_results(self, t, poll_id):
|
|
self.end_poll(self.get_poll(poll_id))
|
|
|
|
def end_poll(self, poll):
|
|
self.bot.send_private_message(poll.char_id,
|
|
f"Your poll <highlight>{poll.id:d}. {poll.question}</highlight> has finished.")
|
|
self.db.exec("UPDATE poll SET is_finished = 1 WHERE id = ?", [poll.id])
|