diff --git a/fastapi-facebook-webhook/.env b/fastapi-facebook-webhook/.env new file mode 100644 index 0000000000000000000000000000000000000000..c121d37896f1db2b77d63ebb1194507c07843979 --- /dev/null +++ b/fastapi-facebook-webhook/.env @@ -0,0 +1,3 @@ +VERIFY_TOKEN=my_token +PAGE_ACCESS_TOKEN=104099084771493 +MONGO_DETAILS=mongodb://user:password@localhost:27017 \ No newline at end of file diff --git a/fastapi-facebook-webhook/app/.gititnore b/fastapi-facebook-webhook/app/.gititnore new file mode 100644 index 0000000000000000000000000000000000000000..bca813b3e75c7635d697ea6d19507c508505ae77 --- /dev/null +++ b/fastapi-facebook-webhook/app/.gititnore @@ -0,0 +1,126 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ +docs/_build/doctrees +docs/_build/html +docs/_build/latex +docs/_build/man +docs/_build/rst +docs/_build/texinfo +docs/_build/xml + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# dotenv +.env +.env.* + +# virtualenv +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# VS Code +.vscode/ + +# Local IDE settings +.idea/ \ No newline at end of file diff --git a/fastapi-facebook-webhook/app/__init__.py b/fastapi-facebook-webhook/app/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/fastapi-facebook-webhook/app/__pycache__/__init__.cpython-310.pyc b/fastapi-facebook-webhook/app/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..24cca05b66609fff0e93a73e0d3d1d777477a32c Binary files /dev/null and b/fastapi-facebook-webhook/app/__pycache__/__init__.cpython-310.pyc differ diff --git a/fastapi-facebook-webhook/app/__pycache__/database.cpython-310.pyc b/fastapi-facebook-webhook/app/__pycache__/database.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..59acab65ae5b6864f9c696a97e35918d37abdf83 Binary files /dev/null and b/fastapi-facebook-webhook/app/__pycache__/database.cpython-310.pyc differ diff --git a/fastapi-facebook-webhook/app/__pycache__/main.cpython-310.pyc b/fastapi-facebook-webhook/app/__pycache__/main.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aa56cc177156a31958f54362817bda8dbca6e336 Binary files /dev/null and b/fastapi-facebook-webhook/app/__pycache__/main.cpython-310.pyc differ diff --git a/fastapi-facebook-webhook/app/__pycache__/models.cpython-310.pyc b/fastapi-facebook-webhook/app/__pycache__/models.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7071ec7f65f986e6a5fb2d6afd0200e550c92277 Binary files /dev/null and b/fastapi-facebook-webhook/app/__pycache__/models.cpython-310.pyc differ diff --git a/fastapi-facebook-webhook/app/__pycache__/schemas.cpython-310.pyc b/fastapi-facebook-webhook/app/__pycache__/schemas.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..674123afa998bbdc9d8efd5ee1e5864aea3072ff Binary files /dev/null and b/fastapi-facebook-webhook/app/__pycache__/schemas.cpython-310.pyc differ diff --git a/fastapi-facebook-webhook/app/database.py b/fastapi-facebook-webhook/app/database.py new file mode 100644 index 0000000000000000000000000000000000000000..51361dfda50270bd7907bbfe9f88dd91ecbdd934 --- /dev/null +++ b/fastapi-facebook-webhook/app/database.py @@ -0,0 +1,8 @@ +from motor.motor_asyncio import AsyncIOMotorClient +import os + +MONGO_DETAILS = os.getenv("MONGO_DETAILS", "mongodb://user:password@localhost:27017") + +client = AsyncIOMotorClient(MONGO_DETAILS) +database = client.webhook_db +log_collection = database.get_collection("logs") \ No newline at end of file diff --git a/fastapi-facebook-webhook/app/main.py b/fastapi-facebook-webhook/app/main.py new file mode 100644 index 0000000000000000000000000000000000000000..29d1f4b29fe79a6bbbb1c1ad2a79ba1e94869cee --- /dev/null +++ b/fastapi-facebook-webhook/app/main.py @@ -0,0 +1,84 @@ +from fastapi import FastAPI, Request, HTTPException, Depends +from .database import log_collection +from bson import ObjectId +import logging +import os +from dotenv import load_dotenv +import httpx +import json + +load_dotenv() # Load environment variables from .env file + +app = FastAPI() + +logging.basicConfig(level=logging.INFO) + +VERIFY_TOKEN = os.getenv("VERIFY_TOKEN", "my_token") +PAGE_ACCESS_TOKEN = os.getenv("PAGE_ACCESS_TOKEN") + +async def send_message(page_access_token: str, recipient_id: str, message_text: str, message_type: str = "UPDATE"): + """ + Send message to specific user (by recipient ID) from specific page (by access token). + + Arguments: + page_access_token: (string) Target page access token. + recipient_id: (string) The ID of the user that the message is addressed to. + message_text: (string) The content of the message. + message_type: (string) The type of the target message. + """ + r = httpx.post( + "https://graph.facebook.com/v2.6/me/messages", + params={"access_token": page_access_token}, + headers={"Content-Type": "application/json"}, + json={ + "recipient": {"id": recipient_id}, + "message": {"text": message_text}, + "messaging_type": message_type, + }, + ) + r.raise_for_status() + +@app.get("/messaging-webhook") +async def verify_webhook(request: Request): + mode = request.query_params.get("hub.mode") + token = request.query_params.get("hub.verify_token") + challenge = request.query_params.get("hub.challenge") + logging.info(f"Received mode: {mode}, token: {token}, challenge: {challenge}, expected token: {VERIFY_TOKEN}") + + if mode and token: + if mode == "subscribe" and token == VERIFY_TOKEN: + logging.info("WEBHOOK_VERIFIED") + return int(challenge) + else: + logging.error("Invalid token or mode") + raise HTTPException(status_code=403, detail="Invalid token or mode") + else: + logging.error("Missing mode or token") + raise HTTPException(status_code=400, detail="Missing mode or token") + +@app.post("/messaging-webhook") +async def receive_webhook(request: Request): + try: + webhook_data = await request.json() + logging.info(f"Received webhook data: {webhook_data}") + + # Insert the raw webhook data into MongoDB + result = await log_collection.insert_one(webhook_data) + logging.info(f"Inserted log with ID: {result.inserted_id}") + + # Extract sender_id and message_text for sending a response + for entry in webhook_data.get("entry", []): + for messaging in entry.get("messaging", []): + sender_id = messaging.get("sender", {}).get("id") + message_text = messaging.get("message", {}).get("text") + if sender_id and message_text: + await send_message( + page_access_token=PAGE_ACCESS_TOKEN, + recipient_id=sender_id, + message_text=f"echo: {message_text}" + ) + + return {"status": "success"} + except Exception as e: + logging.error(f"Error processing webhook: {e}") + raise HTTPException(status_code=400, detail=str(e)) \ No newline at end of file diff --git a/fastapi-facebook-webhook/app/models.py b/fastapi-facebook-webhook/app/models.py new file mode 100644 index 0000000000000000000000000000000000000000..50222b8d4f552435f5da4cd897e7d8e9456eaf3a --- /dev/null +++ b/fastapi-facebook-webhook/app/models.py @@ -0,0 +1,9 @@ +# models.py +from pydantic import BaseModel, Field +from typing import Optional + +class Log(BaseModel): + id: Optional[str] = Field(None, alias="_id") + sender_id: str + message: str + timestamp: str \ No newline at end of file diff --git a/fastapi-facebook-webhook/app/schemas.py b/fastapi-facebook-webhook/app/schemas.py new file mode 100644 index 0000000000000000000000000000000000000000..e41b9e53a2abdac88483053a4ede5fc0721978fc --- /dev/null +++ b/fastapi-facebook-webhook/app/schemas.py @@ -0,0 +1,22 @@ +from pydantic import BaseModel +from typing import List + +class Message(BaseModel): + mid: str + seq: int + text: str + +class Messaging(BaseModel): + sender: dict + recipient: dict + timestamp: int + message: Message + +class Entry(BaseModel): + id: str + time: int + messaging: List[Messaging] + +class WebhookData(BaseModel): + object: str + entry: List[Entry] \ No newline at end of file diff --git a/fastapi-facebook-webhook/docker-compose.yml b/fastapi-facebook-webhook/docker-compose.yml new file mode 100644 index 0000000000000000000000000000000000000000..3f4b0f256e6420a80fe1eb6126f50c15c68ecd80 --- /dev/null +++ b/fastapi-facebook-webhook/docker-compose.yml @@ -0,0 +1,11 @@ +version: '3.1' + +services: + db: + image: mongo + restart: always + environment: + MONGO_INITDB_ROOT_USERNAME: user + MONGO_INITDB_ROOT_PASSWORD: password + ports: + - "27017:27017" \ No newline at end of file diff --git a/fastapi-facebook-webhook/requirements.txt b/fastapi-facebook-webhook/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..025348cc942b05c87e02b0295143e0438e4eb618 --- /dev/null +++ b/fastapi-facebook-webhook/requirements.txt @@ -0,0 +1,6 @@ +fastapi +uvicorn +sqlalchemy +psycopg2-binary +pydantic +httpx \ No newline at end of file