Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
182 changes: 4 additions & 178 deletions modules/questions.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
"""
Querying question database.

This module is also responsible for automatically posting questions coda questions to channels
1. `Not started`: Every 6 hours Stampy posts to `#general` a question with status `Not started`, chosen randomly from those that were least recently posted to Discord. Stampy doesn't post, if the last message in `#general` was this kind of autoposted question.
2. **WIP**: Every Monday, Wednesday, and Friday, sometime between 8 and 12 AM, Stampy posts to `#meta-editing` 1 to 3 questions with status `In review`, `In progress` or `Bulletpoint sketch` that have not been edited for longer than a week. Similarly, he skips if the last message in `#meta-editing` was this one.

How many questions, Count questions
Count questions, optionally queried by status and/or tag
`s, count questions [with status <status>] [tagged <tag>]`
Expand All @@ -25,36 +21,28 @@
Refresh bot's questions cache so that it's in sync with coda. (Only for bot devs and editors/reviewers)
`s, <refresh/reload> questions`
"""

from __future__ import annotations

from datetime import date, datetime, timedelta
import random
from datetime import datetime
import re
from typing import cast, Optional

from discord.channel import TextChannel
from dotenv import load_dotenv

from api.coda import (
CodaAPI,
filter_on_tag,
get_least_recently_asked_on_discord,
)
from api.utilities.coda_utils import REVIEW_STATUSES, QuestionRow, QuestionStatus
from config import coda_api_token, is_rob_server
from servicemodules.discordConstants import (
general_channel_id,
ai_safety_questions_channel_id,
)
from api.utilities.coda_utils import QuestionRow, QuestionStatus
from config import coda_api_token
from modules.module import Module, Response
from utilities.utilities import (
has_permissions,
is_in_testing_mode,
pformat_to_codeblock,
)
from utilities.serviceutils import ServiceMessage
from utilities.time_utils import get_last_monday


if coda_api_token is not None:
from utilities.question_query_utils import (
Expand All @@ -70,9 +58,6 @@


class Questions(Module):
AUTOPOST_NOT_STARTED_MSG_PREFIX = "Recently I've been wondering..."
AUTOPOST_STAGNANT_MSG_PREFIX = "Would any of you like to pick these up?"

@staticmethod
def is_available() -> bool:
return coda_api_token is not None and not is_in_testing_mode()
Expand All @@ -89,32 +74,6 @@ def __init__(self) -> None:
super().__init__()
self.coda_api = CodaAPI.get_instance()

###################
# Autoposting #
###################

# How often Stampy posts random not started questions to `#general`
self.not_started_question_autopost_interval = timedelta(hours=24)

# Time of last (attempted) autopost of not started question
self.last_not_started_autopost_attempt_dt = (
datetime.now() - self.not_started_question_autopost_interval / 2
)

# Date of last (attempted) autopost of WIP question(s)
self.last_wip_autopost_attempt_date: date = get_last_monday().date()

# Max number of WIP questions to be autoposted
self.wip_autopost_limit: int = 3

if is_rob_server:
@self.utils.client.event # fmt:skip
async def on_socket_event_type(_event_type) -> None:
if self.is_time_for_autopost_not_started():
await self.autopost_not_started()
if self.is_time_for_autopost_wip():
await self.autopost_wip()

###############
# Regexes #
###############
Expand Down Expand Up @@ -354,139 +313,6 @@ async def cb_post_questions(
why=why,
)

def is_time_for_autopost_not_started(self) -> bool:
return (
self.last_not_started_autopost_attempt_dt
< datetime.now() - self.not_started_question_autopost_interval
)

async def last_msg_in_general_was_autoposted(self) -> bool:
channel = cast(
TextChannel, self.utils.client.get_channel(int(general_channel_id))
)
async for msg in channel.history(limit=20):
if msg.content.startswith(self.AUTOPOST_NOT_STARTED_MSG_PREFIX):
return True
return False

async def autopost_not_started(self) -> None:
"""Choose a random question from the oldest not started questions and post to `#general` channel"""
current_time = datetime.now()
self.last_not_started_autopost_attempt_dt = current_time

if await self.last_msg_in_general_was_autoposted():
self.log.info(
self.class_name,
msg="Last message in #general was an autoposted question with status `Not started` -> skipping autoposting",
)
return

self.log.info(
self.class_name,
msg="Autoposting a question with status `Not started` to #general",
)
questions_df = self.coda_api.questions_df.query("status == 'Not started'")
questions_df = questions_df[
questions_df["tags"].map(lambda tags: "Stampy" not in tags)
]
if questions_df.empty:
self.log.info(
self.class_name,
msg='Found no questions with status `Not started` without tag "Stampy"',
)
return

question = random.choice(
self.coda_api.q_df_to_rows(
get_least_recently_asked_on_discord(questions_df)
)
)

channel = cast(
TextChannel, self.utils.client.get_channel(int(general_channel_id))
)

msg = f"{self.AUTOPOST_NOT_STARTED_MSG_PREFIX}\n\n{make_post_question_message(question)}"
self.coda_api.update_question_last_asked_date(question, current_time)
self.coda_api.last_question_id = question["id"]

await channel.send(msg)

def is_time_for_autopost_wip(self) -> bool:
now = datetime.now()
return (
now.weekday() in (0, 4) # Monday or Friday
and 8 <= now.hour <= 12 # between 08:00 and 12:00
and self.last_wip_autopost_attempt_date
!= now.date() # Wasn't posted today yet
)

async def last_msg_in_ai_safety_questions_was_autoposted(self) -> bool:
channel = cast(
TextChannel,
self.utils.client.get_channel(int(ai_safety_questions_channel_id)),
)
async for msg in channel.history(limit=1):
if msg.content.startswith(self.AUTOPOST_STAGNANT_MSG_PREFIX):
return True
return False

async def autopost_wip(self) -> None:
"""Post up to a specified number of questions that have been worked on but not touched for longer than a week
to #meta-editing channel."""
today = date.today()
self.last_wip_autopost_attempt_date = today

if await self.last_msg_in_ai_safety_questions_was_autoposted():
self.log.info(
self.class_name,
msg="Last message in `#meta-editing` was one or more autoposted WIP question(s) -> skipping autoposting",
)
return

week_ago = today - timedelta(days=7)
question_limit = random.randint(1, self.wip_autopost_limit)

questions_df = self.coda_api.questions_df.query(
f"doc_last_edited <= '{week_ago}'"
)
questions_df = (
questions_df[
questions_df["status"].map(lambda status: status in REVIEW_STATUSES)
]
.sort_values(["last_asked_on_discord", "doc_last_edited"])
.head(question_limit)
)

if questions_df.empty:
self.log.info(
self.class_name,
msg=f"Found no questions with status from {REVIEW_STATUSES} with docs edited one week ago or earlier",
)
return

questions = self.coda_api.q_df_to_rows(questions_df)

self.log.info(
self.class_name,
msg=f"Posting {len(questions)} WIP questions to #meta-editing",
)

if len(questions) == 1:
self.coda_api.last_question_id = questions[0]["id"]

channel = cast(
TextChannel,
self.utils.client.get_channel(int(ai_safety_questions_channel_id)),
)
current_time = datetime.now()
msg = self.AUTOPOST_STAGNANT_MSG_PREFIX + "\n\n"
for q in questions:
msg += f"{make_post_question_message(q, with_status=True, with_doc_last_edited=True)}\n"
self.coda_api.update_question_last_asked_date(q, current_time)

await channel.send(msg)

#########################
# Get question info #
#########################
Expand Down