added inline emojis, and on_edit for tenor and also the bot only saves gifv embeds instead of all images/videos whatever links... i think (i asked claude to do that for me because i couldn't figure it out) if it works as intended it should only save gifs and links ending with .gif

This commit is contained in:
david committed 2026-10-21 20:16:19 -03:00
1 parent d704581548
commit dc06343909
1 file changed
+34 -10
+34 -10
View File
@@ -85,7 +85,8 @@ QUOTA_COOLDOWN = 3600 # after gemini says it's out of quota go straight to the
quota_until = 0.0 # unix timestamp
MAX_REACTION_OPTIONS = 25 # per kind (stickers/emojis/media), random sample so the prompt stays small
REACTION_TAG = re.compile(r'\[(sticker|emoji|media):\s*([^\]]+?)\s*\]', re.I)
REACTION_TAG = re.compile(r'\[(sticker|reaction|media):\s*([^\]]+?)\s*\]', re.I)
EMOJI_TAG = re.compile(r'\[emoji:\s*([^\]]+?)\s*\]', re.I) # inline, this one can go anywhere in the message
QUIET_COOLDOWN = 600 # seconds of silence before she's allowed to send a random message (10 mins)
@@ -297,11 +298,11 @@ async def steal_attachments(message):
]
for embed in message.embeds:
#tenor/klipy links come as gifv embeds with an mp4 in embed.video direct gif links are plain image embeds
# tenor/klipy links come as "gifv" embeds with an mp4 in embed.video, direct .gif links are plain image embeds
for part in (embed.image, embed.video):
if part and part.url:
ext = Path(urlparse(part.url).path).suffix # no extension no preview on discord not worth keeping
if ext and (embed.type == "gifv" or ext.lower() == ".gif"):
ext = Path(urlparse(part.url).path).suffix # no extension, no preview on discord, not worth keeping
if ext and (embed.type == "gifv" or ext.lower() == ".gif"):
media.append((part.url, hashlib.sha1(part.url.encode()).hexdigest()[:16] + ext))
for url, name in media:
@@ -456,13 +457,23 @@ def reaction_tool_prompt(guild) -> str:
sections.append(f"{key}:\n" + "\n".join(f"- {n}: {d}" if d else f"- {n}" for n, d in options))
if not sections:
return ""
return ("\n\nREACTIONS: you can send a sticker, an emoji reaction or a media file instead of (or along with) words, but only when it really fits the moment, most of the time just reply normally. "
"to do it put ONE tag at the end of your message: [sticker: name], [emoji: name] or [media: name], using exactly one of the names below. "
"the tag alone is fine if you have nothing to say. never invent names.\n" + "\n".join(sections))
emoji_note = ("\n\nyou can also put a server emoji inside the text of your message with [emoji: name], like \"hi [emoji: name] hello\", using a name from the emojis list. "
"don't use emojis in every message, most of your messages should have none.") if any(s.startswith("emojis:") for s in sections) else ""
return ("\n\nREACTIONS: you can send a sticker, react to the message with an emoji or send a media file instead of (or along with) words, but only when it really fits the moment, most of the time just reply normally. "
"to do it put ONE tag at the end of your message: [sticker: name], [reaction: name] or [media: name], using exactly one of the names below ([reaction: name] uses the emoji names). "
"the tag alone is fine if you have nothing to say. never invent names.\n" + "\n".join(sections) + emoji_note)
def split_reactions(text: str) -> tuple[str, list]:
return REACTION_TAG.sub("", text).strip(), [(k.lower(), n) for k, n in REACTION_TAG.findall(text)]
def fill_emojis(text: str, guild) -> str:
# [emoji: name] inside a message becomes the real custom emoji, same check as the reactions (has to exist and be available), a made up name just disappears
def replace(m):
name = m.group(1).strip().strip(":").lower()
chosen = next((e for e in guild.emojis if e.available and e.name.lower() == name), None)
return str(chosen) if chosen else ""
return EMOJI_TAG.sub(replace, text)
async def send_reaction(message, kind: str, name: str):
# looks the name up for real, a made up name just does nothing
name = name.strip().strip(":").lower()
@@ -473,7 +484,7 @@ async def send_reaction(message, kind: str, name: str):
await message.channel.send(stickers=[chosen])
log_message(f"[{now_str()}] Faggot-chan sent sticker: {chosen.name}")
print(f"[{now_str()}] Faggot-chan sent sticker: {chosen.name}")
elif kind == "emoji":
elif kind == "reaction":
chosen = next((e for e in message.guild.emojis if e.available and e.name.lower() == name), None)
if chosen:
await message.add_reaction(chosen)
@@ -596,9 +607,10 @@ async def on_message(message):
if not result.strip() and not reactions:
return
shown = fill_emojis(result, message.guild) # what gets sent, result keeps the [emoji: x] tags for the log and history
try:
if result.strip():
await send_reply(message, result)
if shown.strip():
await send_reply(message, shown)
print(f"[{now_str()}] {message.author} said: {content}\n[{now_str()}] Faggot-chan said: {result}")
log_message(f"[{now_str()}] Faggot-chan said: {result}")
if str(message.author.id) == settings["moralfag_id"]:
@@ -611,4 +623,16 @@ async def on_message(message):
except Exception as e:
await handle_error(f"[{now_str()}] failed to send reply: {e}", notify_channel=message.channel)
@client.event
async def on_message_edit(before, after):
# discord builds link embeds (tenor etc) a moment after the message is sent, so message.embeds is still empty in on_message
# and the embed shows up here as an edit instead
if after.author == client.user or after.author.bot or after.guild is None:
return
if not after.embeds or before.embeds: # only when embeds just appeared
return
if after.channel.name not in ["general", "announcements"] or str(after.author.id) in load_blocked():
return
await steal_attachments(after)
client.run(TOKEN)