Add optional display name targeting and startup message catch-up logic
This commit is contained in:
113
main.py
113
main.py
@@ -33,6 +33,10 @@ HISTORY_FILE = Path(os.environ.get("HISTORY_FILE", "chat_history.jsonl"))
|
||||
MAX_TOKENS_HISTORY = int(os.environ.get("MAX_TOKENS_HISTORY", "2200")) # rough token budget
|
||||
MAX_MESSAGES_HISTORY = int(os.environ.get("MAX_MESSAGES_HISTORY", "30"))
|
||||
|
||||
# Optional targeting helpers
|
||||
TARGET_DISPLAY_NAME = os.environ.get("TARGET_DISPLAY_NAME", "").strip()
|
||||
TARGET_CACHE_FILE = Path(os.environ.get("TARGET_CACHE_FILE", "target_id.txt"))
|
||||
|
||||
# ---------- Validation ----------
|
||||
def _require(cond: bool, msg: str):
|
||||
if not cond:
|
||||
@@ -109,6 +113,14 @@ def build_chat_messages_for_openai() -> List[Dict[str, str]]:
|
||||
msgs.append({"role": role, "content": r["content"]})
|
||||
return msgs
|
||||
|
||||
def last_history_record() -> Dict[str, Any] | None:
|
||||
records = load_history()
|
||||
return records[-1] if records else None
|
||||
|
||||
def last_history_role() -> str | None:
|
||||
rec = last_history_record()
|
||||
return rec.get("role") if rec else None
|
||||
|
||||
|
||||
# ---------- OpenAI client ----------
|
||||
oai = OpenAI(api_key=OPENAI_API_KEY)
|
||||
@@ -143,15 +155,97 @@ async def resolve_target_entity(client: TelegramClient):
|
||||
if TARGET_USERNAME:
|
||||
try:
|
||||
target = await client.get_entity(TARGET_USERNAME)
|
||||
if target:
|
||||
return target
|
||||
except Exception:
|
||||
target = None
|
||||
|
||||
if not target and TARGET_USER_ID:
|
||||
try:
|
||||
target = await client.get_entity(TARGET_USER_ID)
|
||||
if target:
|
||||
return target
|
||||
except Exception:
|
||||
target = None
|
||||
|
||||
# Try cached target ID from a previous run
|
||||
if not target and TARGET_CACHE_FILE.exists():
|
||||
try:
|
||||
cached_id = int(TARGET_CACHE_FILE.read_text().strip())
|
||||
target = await client.get_entity(cached_id)
|
||||
if target:
|
||||
return target
|
||||
except Exception:
|
||||
target = None
|
||||
|
||||
# Try resolving by display name across dialogs (case-insensitive exact match)
|
||||
if not target and TARGET_DISPLAY_NAME:
|
||||
try:
|
||||
async for d in client.iter_dialogs():
|
||||
ent = d.entity
|
||||
name = getattr(d, "name", "") or ""
|
||||
if name.strip().lower() == TARGET_DISPLAY_NAME.lower():
|
||||
# Make sure it's a user dialog, not a group
|
||||
if hasattr(ent, "bot") or getattr(ent, "is_self", False):
|
||||
pass
|
||||
return ent
|
||||
except Exception:
|
||||
target = None
|
||||
|
||||
return target
|
||||
|
||||
def cache_target_id(entity) -> None:
|
||||
try:
|
||||
TARGET_CACHE_FILE.write_text(str(getattr(entity, "id", "")))
|
||||
except Exception as e:
|
||||
print(f"Warning: failed to write target cache file: {e}")
|
||||
|
||||
|
||||
async def startup_catchup_if_needed(client: TelegramClient, target_entity) -> bool:
|
||||
"""
|
||||
On startup, if the last message in the target dialog is from them (incoming)
|
||||
and we haven't replied after that in our local history, generate and send a reply.
|
||||
Returns True if a catch-up reply was sent.
|
||||
"""
|
||||
if not target_entity:
|
||||
print("startup_catchup_if_needed: no target resolved")
|
||||
return False
|
||||
|
||||
msgs = await client.get_messages(target_entity, limit=1)
|
||||
if not msgs:
|
||||
print("startup_catchup_if_needed: target dialog has no messages")
|
||||
return False
|
||||
|
||||
last_msg = msgs[0]
|
||||
# Only act if the last message is incoming (from them) and it has text
|
||||
if last_msg.out or not (last_msg.message or "").strip():
|
||||
return False
|
||||
|
||||
# If our last history entry is already an assistant message, we likely replied.
|
||||
role = last_history_role()
|
||||
if role == "assistant":
|
||||
return False
|
||||
|
||||
# Ensure that incoming text is reflected in history before generating a response.
|
||||
last_rec = last_history_record()
|
||||
if not last_rec or last_rec.get("role") != "user" or last_rec.get("content") != last_msg.message:
|
||||
append_history("user", last_msg.message or "")
|
||||
|
||||
try:
|
||||
reply = await generate_reply_via_openai()
|
||||
except Exception as e:
|
||||
print(f"OpenAI error during startup catch-up: {e}")
|
||||
reply = random.choice([
|
||||
"Sorry, just saw this—could you tell me a bit more?",
|
||||
"I had to step away a minute—where were we?",
|
||||
"Interesting—what do you like most about that?",
|
||||
])
|
||||
|
||||
append_history("assistant", reply)
|
||||
await safe_send(client, target_entity, reply)
|
||||
return True
|
||||
|
||||
|
||||
|
||||
async def human_delay():
|
||||
await asyncio.sleep(random.randint(MIN_DELAY_SEC, MAX_DELAY_SEC))
|
||||
@@ -184,11 +278,23 @@ async def main():
|
||||
target_entity = await resolve_target_entity(client)
|
||||
if target_entity:
|
||||
print(f"Target resolved: id={target_entity.id}, username={getattr(target_entity, 'username', None)}")
|
||||
cache_target_id(target_entity)
|
||||
else:
|
||||
print("Target not resolved yet. Will match dynamically on first incoming message from target.")
|
||||
if TARGET_USERNAME:
|
||||
print(f"Hint: couldn't resolve by username '{TARGET_USERNAME}'. Check spelling and privacy.")
|
||||
if TARGET_USER_ID:
|
||||
print(f"Hint: couldn't resolve by user id '{TARGET_USER_ID}'.")
|
||||
if TARGET_DISPLAY_NAME:
|
||||
print(f"Hint: couldn't resolve by display name '{TARGET_DISPLAY_NAME}'.")
|
||||
|
||||
# Optional: send a gentle opener once (only if history is empty)
|
||||
if not HISTORY_FILE.exists():
|
||||
# If we already have a target, attempt a startup catch-up reply if their message was last.
|
||||
catchup_sent = False
|
||||
if target_entity:
|
||||
catchup_sent = await startup_catchup_if_needed(client, target_entity)
|
||||
|
||||
# Optional: send a gentle opener once (only if history is empty and we didn't just catch up)
|
||||
if not HISTORY_FILE.exists() and not catchup_sent:
|
||||
opener = "Oh neat, Houston. I'm up in the Pacific Northwest these days, sort of near the coast. What brought you from the UK to Houston?"
|
||||
append_history("assistant", opener)
|
||||
if target_entity:
|
||||
@@ -204,9 +310,10 @@ async def main():
|
||||
return
|
||||
|
||||
# If target not yet resolved, auto-resolve on first qualifying message
|
||||
if (not target_entity) and (TARGET_USER_ID == 0 and not TARGET_USERNAME):
|
||||
if (not target_entity) and (TARGET_USER_ID == 0 and not TARGET_USERNAME and not TARGET_DISPLAY_NAME):
|
||||
# No explicit target provided; first inbound DM will become the target
|
||||
target_entity = sender
|
||||
cache_target_id(target_entity)
|
||||
print(f"Auto-targeted sender id={sender.id}, username={sender.username}")
|
||||
|
||||
if not sender_matches_target(sender, target_entity):
|
||||
|
||||
Reference in New Issue
Block a user