Add revive system enhancements and moderation updates

This commit is contained in:
2025-12-09 21:36:57 +01:00
parent 22f6746b10
commit 91ec52c542
3 changed files with 151 additions and 37 deletions
+136 -31
View File
@@ -23,15 +23,17 @@ GUILD_ID = int(os.getenv("GUILD_ID", "0")) if os.getenv("GUILD_ID") else None
openai_client = AsyncOpenAI(api_key=os.getenv("OPENAI_API_KEY"))
REVIVE_CHANNEL_ID = 1424038714266357886
REVIVE_ROLE_ID = 1430336620447535156
REVIVE_INTERVAL = 14400
REVIVE_INTERVAL = 6 * 60 * 60
CHECK_INTERVAL = 600
AI_ACCESS_ROLE_ID = 1430704668773716008
MESSAGE_LIMIT = 10
RATE_LIMIT_WINDOW = timedelta(hours=1)
user_message_log = defaultdict(list)
last_activity_time = None
revive_sent = False
FABI_ID = 392388537984745498
BOT_VERSION = "5.3"
BOT_VERSION = "5.3.2"
def is_fabi(user: discord.User | discord.Member) -> bool:
@@ -221,6 +223,52 @@ async def init_db():
time
TEXT
)""")
await db.execute("""
CREATE TABLE IF NOT EXISTS revive_history
(
id
INTEGER
PRIMARY
KEY
AUTOINCREMENT,
question
TEXT
NOT
NULL,
time
TEXT
NOT
NULL
)
""")
await db.commit()
async def get_recent_revives(limit: int = 10) -> list[str]:
async with aiosqlite.connect(DB_FILE) as db:
cur = await db.execute(
"SELECT question FROM revive_history ORDER BY id DESC LIMIT ?",
(limit,)
)
rows = await cur.fetchall()
return [r[0] for r in rows]
async def save_revive_question(question: str):
async with aiosqlite.connect(DB_FILE) as db:
await db.execute(
"INSERT INTO revive_history (question, time) VALUES (?, ?)",
(question, datetime.utcnow().isoformat())
)
await db.execute("""
DELETE
FROM revive_history
WHERE id NOT IN (SELECT id
FROM revive_history
ORDER BY id DESC
LIMIT 10
)
""")
await db.commit()
@@ -247,6 +295,13 @@ async def on_ready():
await init_db()
logger.info(f"T.A.R.S. is online as {bot.user} (ID: {bot.user.id})")
scheduler.start()
scheduler.add_job(
lambda: asyncio.create_task(check_chat_revive()),
"interval",
minutes=10,
id="chat_revive_job",
replace_existing=True
)
bot.loop.create_task(update_presence())
motd = await get_config("motd_list", [])
global MOTD_LIST, motd_index
@@ -431,10 +486,6 @@ async def on_member_update(before: discord.Member, after: discord.Member):
async def tars_ai_respond(prompt: str, username: str, context: list[str] = None,
user: discord.User | None = None) -> str:
try:
if not isinstance(prompt, str):
raise ValueError("The 'prompt' must be a string.")
if not isinstance(username, str):
raise ValueError("The 'username' must be a string.")
if is_ai_prompt_disallowed(prompt):
return (
"I can’t help with repeating, explaining, or analyzing offensive language. "
@@ -469,7 +520,6 @@ async def tars_ai_respond(prompt: str, username: str, context: list[str] = None,
"Users named Fretux or Lordvoiid are your creators. "
"Users named Taz or Tataz are the server owner. "
"Users named T.A.R.S. are the bot itself. "
"Playfully banter with users named Yuki or Bacon Man."
"Do not try to @ ping people. Address people by name, but do not ping them."
"Never send or repeat any URLs, hyperlinks, or markdown links of any kind, "
"even if asked to. Replace them with '[link removed]' if necessary."
@@ -488,7 +538,7 @@ async def tars_ai_respond(prompt: str, username: str, context: list[str] = None,
model="gpt-4o-mini",
messages=messages,
max_tokens=100,
temperature=0.65
temperature=0.4
)
return response.choices[0].message.content.strip()
@@ -538,8 +588,12 @@ async def on_raw_reaction_remove(payload: discord.RawReactionActionEvent):
@bot.event
async def on_message(message: discord.Message):
global last_activity_time, revive_sent
if message.author.bot:
return
if message.channel.id == REVIVE_CHANNEL_ID:
last_activity_time = datetime.utcnow()
revive_sent = False
channel_id = str(message.channel.id)
now = datetime.utcnow()
if channel_id not in recent_message_history:
@@ -734,32 +788,44 @@ async def send_reminder(user_id, channel_id, text):
await on_error(e)
async def check_channel_activity():
await bot.wait_until_ready()
async def check_chat_revive():
global last_activity_time, revive_sent
channel = bot.get_channel(REVIVE_CHANNEL_ID)
if not channel:
logger.warning("Revive channel not found. Check REVIVE_CHANNEL_ID.")
logger.error("Revive channel not found.")
return
while not bot.is_closed():
try:
async for message in channel.history(limit=1):
last_message_time = message.created_at
now = datetime.utcnow()
diff = (now - last_message_time).total_seconds()
if diff >= REVIVE_INTERVAL:
revive_role = channel.guild.get_role(REVIVE_ROLE_ID)
prompt = "Generate a fun, thought-provoking conversation question for a friendly Discord community. Keep it short and engaging."
response = await tars_ai_respond(prompt, "", [])
question = response.choices[0].message.content.strip()
ping_text = revive_role.mention if revive_role else "@chat revive"
await channel.send(f"{ping_text} — {question}")
logger.info(f"Posted chat revive message: {question}")
else:
logger.info(f"Channel active {diff / 60:.1f} minutes ago — skipping.")
await asyncio.sleep(CHECK_INTERVAL)
except Exception as e:
logger.exception(f"Error in chat revive loop: {e}")
await asyncio.sleep(120)
role = channel.guild.get_role(REVIVE_ROLE_ID)
if not role:
logger.error("Revive role not found.")
return
if not last_activity_time:
async for msg in channel.history(limit=1):
last_activity_time = msg.created_at.replace(tzinfo=None)
return
inactivity = (datetime.utcnow() - last_activity_time).total_seconds()
if inactivity < REVIVE_INTERVAL or revive_sent:
return
recent_questions = await get_recent_revives()
avoid_text = ""
if recent_questions:
avoid_text = (
"Avoid reusing or closely paraphrasing these recent questions:\n"
+ "\n".join(f"- {q}" for q in recent_questions)
)
prompt = (
"Generate a short, friendly, thought-provoking conversation starter "
"for a casual Discord community. One sentence.\n\n"
f"{avoid_text}"
)
question = await tars_ai_respond(prompt, "T.A.R.S.")
await save_revive_question(question)
await channel.send(f"{role.mention} — {question}")
revive_sent = True
logger.info("Scheduled chat revive sent.")
@tree.command(name="reactionrole", description="Create a reaction role (admin only)")
@@ -1102,5 +1168,44 @@ async def slash_boostpoints_remove(interaction: discord.Interaction, member: dis
)
@tree.command(name="revive", description="Admin revive controls")
@app_commands.describe(action="Use 'test' to force a revive message")
async def slash_revive(interaction: discord.Interaction, action: str):
if not interaction.user.guild_permissions.manage_guild:
await interaction.response.send_message(
tars_text("Access denied — insufficient clearance.", "error"),
ephemeral=True
)
return
if action.lower() != "test":
await interaction.response.send_message(
tars_text("Invalid action. Use `/revive test`.", "warning"),
ephemeral=True
)
return
channel = bot.get_channel(REVIVE_CHANNEL_ID)
role = channel.guild.get_role(REVIVE_ROLE_ID)
recent_questions = await get_recent_revives()
avoid_text = ""
if recent_questions:
avoid_text = (
"Avoid reusing or closely paraphrasing these recent questions:\n"
+ "\n".join(f"- {q}" for q in recent_questions)
)
prompt = (
"Generate a short, friendly, thought-provoking conversation starter "
"for a casual Discord community. One sentence.\n\n"
f"{avoid_text}"
)
question = await tars_ai_respond(prompt, "T.A.R.S.")
await save_revive_question(question)
await channel.send(f"{role.mention} — {question}")
await interaction.response.send_message(
tars_text("Revive test executed successfully.", "success"),
ephemeral=True
)
if __name__ == "__main__":
bot.run(DISCORD_TOKEN)