monk / post_to_facebook.py
hf-actions
feat: persistent retry queue for failed FB posts; enqueue on failures; start worker
5f2de2a
import os
import argparse
import logging
import requests
import socket
import time
from requests.exceptions import RequestException
from dotenv import load_dotenv
from retry_queue import enqueue as enqueue_retry
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
logger = logging.getLogger(__name__)
def _append_log(entry: str):
try:
with open("log.txt", "a", encoding="utf-8") as lf:
lf.write(entry + "\n")
except Exception:
logger.exception("Failed to write to log.txt")
def post_to_facebook(page_id: str, access_token: str, message: str, link: str | None = None):
url = f"https://graph.facebook.com/{page_id}/feed"
payload = {"message": message, "access_token": access_token}
if link:
payload["link"] = link
logger.info("Posting to Facebook page %s", page_id)
# Ensure graph.facebook.com resolves before attempting to POST to avoid long, noisy stack traces
try:
socket.getaddrinfo("graph.facebook.com", 443)
except Exception as e:
logger.error("DNS resolution failed for graph.facebook.com: %s", e)
_append_log(f"[{__import__('time').strftime('%Y-%m-%d %H:%M:%S')}] FB_DNS_RESOLUTION_FAILED {e}")
# enqueue for later retry
try:
enqueue_retry({"type": "message", "page_id": page_id, "access_token": access_token, "message": message, "link": link})
except Exception:
logger.exception("Failed to enqueue message after DNS failure")
raise
# Retry with exponential backoff for transient network issues
attempts = 4
backoff = 2
last_exc = None
for attempt in range(1, attempts + 1):
try:
resp = requests.post(url, data=payload, timeout=15)
resp.raise_for_status()
last_exc = None
break
except RequestException as e:
last_exc = e
logger.warning("Attempt %s/%s: Facebook POST failed: %s", attempt, attempts, e)
if attempt < attempts:
time.sleep(backoff)
backoff *= 2
else:
logger.error("All Facebook POST attempts failed")
_append_log(f"[{__import__('time').strftime('%Y-%m-%d %H:%M:%S')}] FB_POST_ERROR page={page_id} message={message[:120]!r} exception={e}")
# enqueue for retry
try:
enqueue_retry({"type": "message", "page_id": page_id, "access_token": access_token, "message": message, "link": link})
except Exception:
logger.exception("Failed to enqueue failed message post")
raise
data = resp.json()
logger.info("Post successful: %s", data)
# append a concise entry to log.txt for auditing
try:
_append_log(f"[{__import__('time').strftime('%Y-%m-%d %H:%M:%S')}] FB_POST_SUCCESS page={page_id} id={data.get('id')} post_id={data.get('post_id')}")
except Exception:
logger.exception("Failed to append FB success to log.txt")
return data
def main():
load_dotenv()
parser = argparse.ArgumentParser(description="Post a message to a Facebook Page using Page Access Token from .env")
parser.add_argument("-m", "--message", help="Message text to post (or set MESSAGE in .env)")
parser.add_argument("-l", "--link", help="Optional link to include")
parser.add_argument("--use-app-access", action="store_true", help="Attempt to post using the app access token (APP_ID|APP_SECRET)")
args = parser.parse_args()
# Prefer explicit env names already present in the repo
page_id = os.getenv("FB_PAGE_ID") or os.getenv("PAGE_ID")
access_token = os.getenv("FB_PAGE_ACCESS_TOKEN") or os.getenv("PAGE_ACCESS_TOKEN")
message = args.message or os.getenv("MESSAGE")
link = args.link or os.getenv("LINK")
use_app_access = args.use_app_access
# build app access token if requested
if use_app_access:
app_id = os.getenv("FB_APP_ID")
app_secret = os.getenv("FB_APP_SECRET")
if not app_id or not app_secret:
print("ERROR: FB_APP_ID and FB_APP_SECRET required to build app access token")
raise SystemExit(1)
access_token = f"{app_id}|{app_secret}"
if not page_id or not access_token:
logger.error("Missing FB_PAGE_ID or FB_PAGE_ACCESS_TOKEN in environment (.env)")
raise SystemExit(1)
if not message:
logger.error("No message provided (use --message or set MESSAGE in .env)")
raise SystemExit(1)
try:
result = post_to_facebook(page_id, access_token, message, link)
except requests.HTTPError as e:
details = e.response.text if e.response is not None else str(e)
logger.error("Facebook API request failed: %s", details)
raise SystemExit(1)
logger.info("Posted to Facebook page successfully: %s", result)
print("Posted to Facebook page successfully:", result)
if __name__ == "__main__":
main()