mirror of
https://github.com/x1xhlol/system-prompts-and-models-of-ai-tools.git
synced 2026-06-17 23:09:35 +00:00
Complete AI-powered personal brand automation for Sami Assiri.\n\n7 agents: LinkedIn, Email, Social Media, WhatsApp, CV Optimizer, Content Strategist, Opportunity Scout.\nInfra: FastAPI + APScheduler + Docker + Ollama/Groq LLM + GitHub Pages landing page.\n83 files, ~10K lines. Cost: $0-5/month.
211 lines
6.1 KiB
Python
211 lines
6.1 KiB
Python
"""Engage with the LinkedIn feed -- like and comment on relevant posts."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import random
|
|
from pathlib import Path
|
|
|
|
import yaml
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
_TEMPLATES_DIR = Path(__file__).resolve().parent / "prompts"
|
|
|
|
# Topics Sami cares about (used for relevance filtering)
|
|
TARGET_KEYWORDS = [
|
|
"airport security",
|
|
"aviation",
|
|
"smiths detection",
|
|
"metco",
|
|
"x-ray",
|
|
"screening",
|
|
"baggage",
|
|
"checkpoint",
|
|
"ct scanner",
|
|
"trace detection",
|
|
"eds",
|
|
"engineering",
|
|
"field service",
|
|
"epc",
|
|
"gaca",
|
|
"icao",
|
|
"saudi arabia",
|
|
"spe",
|
|
"petroleum",
|
|
"oil and gas",
|
|
"أمن المطارات",
|
|
"هندسة",
|
|
"الطيران",
|
|
]
|
|
|
|
COMMENT_SYSTEM_PROMPT = """\
|
|
You are writing a LinkedIn comment on behalf of Sami Mohammed Assiri, a Field \
|
|
Services Engineer at METCO (Smiths Detection) specialising in airport security \
|
|
technology.
|
|
|
|
Guidelines:
|
|
- Be genuine and insightful -- add real value, not generic praise.
|
|
- Reference a specific point from the post when possible.
|
|
- Keep it between 1 and 3 sentences.
|
|
- Maintain a professional yet warm tone.
|
|
- Do NOT be sycophantic ("Great post!", "Love this!", "Amazing insight!").
|
|
- If the post is in Arabic, comment in Arabic. Otherwise, use English.
|
|
- Never self-promote or include links.
|
|
"""
|
|
|
|
|
|
def _load_comment_templates() -> dict:
|
|
"""Load comment_templates.yaml."""
|
|
path = _TEMPLATES_DIR / "comment_templates.yaml"
|
|
if not path.exists():
|
|
return {}
|
|
with open(path, "r", encoding="utf-8") as fh:
|
|
return yaml.safe_load(fh) or {}
|
|
|
|
|
|
def _is_relevant(post_text: str) -> bool:
|
|
"""Rough keyword check to decide if a post is worth engaging with."""
|
|
lower = post_text.lower()
|
|
return any(kw in lower for kw in TARGET_KEYWORDS)
|
|
|
|
|
|
def _extract_post_text(post: dict) -> str:
|
|
"""Safely pull the textual content from a linkedin-api post dict."""
|
|
try:
|
|
commentary = (
|
|
post.get("commentary", "")
|
|
or post.get("specificContent", {})
|
|
.get("com.linkedin.ugc.ShareContent", {})
|
|
.get("shareCommentary", {})
|
|
.get("text", "")
|
|
)
|
|
return commentary or ""
|
|
except (AttributeError, TypeError):
|
|
return ""
|
|
|
|
|
|
def _extract_post_urn(post: dict) -> str | None:
|
|
"""Extract the post URN (entity ID) from a feed post dict."""
|
|
return post.get("dashEntityUrn") or post.get("entityUrn") or post.get("urn")
|
|
|
|
|
|
async def _generate_comment(llm_client, post_text: str, brand_profile: dict) -> str:
|
|
"""Use the LLM to craft a thoughtful comment for the given post."""
|
|
templates = _load_comment_templates()
|
|
|
|
# Provide a few example styles to guide the LLM
|
|
example_block = ""
|
|
categories = list(templates.values()) if templates else []
|
|
if categories:
|
|
flat = [t for cat in categories for t in (cat if isinstance(cat, list) else [])]
|
|
if flat:
|
|
samples = random.sample(flat, min(2, len(flat)))
|
|
example_block = (
|
|
"\nExample comment styles (do NOT copy verbatim):\n"
|
|
+ "\n".join(f"- {s}" for s in samples)
|
|
+ "\n"
|
|
)
|
|
|
|
user_prompt = (
|
|
f"Original LinkedIn post:\n\"\"\"\n{post_text[:1500]}\n\"\"\"\n\n"
|
|
f"{example_block}\n"
|
|
f"Write a comment as Sami. Return ONLY the comment text."
|
|
)
|
|
|
|
response = await llm_client.generate(
|
|
prompt=user_prompt,
|
|
system_prompt=COMMENT_SYSTEM_PROMPT,
|
|
temperature=0.75,
|
|
max_tokens=300,
|
|
)
|
|
return response.text.strip().strip('"')
|
|
|
|
|
|
async def engage_with_feed(
|
|
linkedin_api,
|
|
llm_client,
|
|
brand_profile: dict,
|
|
max_likes: int = 15,
|
|
max_comments: int = 5,
|
|
) -> dict:
|
|
"""Like and comment on recent relevant posts in Sami's LinkedIn feed.
|
|
|
|
Parameters
|
|
----------
|
|
linkedin_api:
|
|
Authenticated ``linkedin_api.Linkedin`` instance.
|
|
llm_client:
|
|
LLM client for generating comments.
|
|
brand_profile:
|
|
Parsed brand profile dict.
|
|
max_likes:
|
|
Maximum number of posts to like in this round.
|
|
max_comments:
|
|
Maximum number of posts to comment on in this round.
|
|
|
|
Returns
|
|
-------
|
|
dict
|
|
Summary of actions taken (likes, comments, errors).
|
|
"""
|
|
liked = 0
|
|
commented = 0
|
|
errors: list[str] = []
|
|
|
|
try:
|
|
feed = linkedin_api.get_feed_posts(limit=50)
|
|
except Exception as exc:
|
|
logger.error("Failed to fetch feed: %s", exc)
|
|
return {"liked": 0, "commented": 0, "errors": [str(exc)]}
|
|
|
|
if not feed:
|
|
logger.info("Feed returned no posts.")
|
|
return {"liked": 0, "commented": 0, "errors": []}
|
|
|
|
# Shuffle to avoid always engaging with the same people
|
|
random.shuffle(feed)
|
|
|
|
for post in feed:
|
|
if liked >= max_likes and commented >= max_comments:
|
|
break
|
|
|
|
post_text = _extract_post_text(post)
|
|
post_urn = _extract_post_urn(post)
|
|
|
|
if not post_urn:
|
|
continue
|
|
|
|
# --- Like ---
|
|
if liked < max_likes:
|
|
try:
|
|
linkedin_api.like(post_urn)
|
|
liked += 1
|
|
logger.debug("Liked post %s", post_urn)
|
|
except Exception as exc:
|
|
errors.append(f"Like failed ({post_urn}): {exc}")
|
|
logger.warning("Failed to like %s: %s", post_urn, exc)
|
|
|
|
# --- Comment (only on relevant posts) ---
|
|
if commented < max_comments and post_text and _is_relevant(post_text):
|
|
try:
|
|
comment_text = await _generate_comment(
|
|
llm_client, post_text, brand_profile
|
|
)
|
|
linkedin_api.comment(post_urn, comment_text)
|
|
commented += 1
|
|
logger.info(
|
|
"Commented on %s: %s", post_urn, comment_text[:80]
|
|
)
|
|
except Exception as exc:
|
|
errors.append(f"Comment failed ({post_urn}): {exc}")
|
|
logger.warning("Failed to comment on %s: %s", post_urn, exc)
|
|
|
|
summary = {
|
|
"liked": liked,
|
|
"commented": commented,
|
|
"errors": errors[:10], # cap stored errors
|
|
}
|
|
logger.info("Engagement round complete: %s", summary)
|
|
return summary
|