From 5140cff75c75730ce7a7271e564acb9026816c4f Mon Sep 17 00:00:00 2001 From: James Abrenica <jamesjirehabrenica@yahoo.com> Date: Fri, 11 Oct 2024 15:04:14 +0800 Subject: [PATCH] added fast api facebook webhook --- fastapi-facebook-webhook/.env | 3 + fastapi-facebook-webhook/app/.gititnore | 126 ++++++++++++++++++ fastapi-facebook-webhook/app/__init__.py | 0 .../app/__pycache__/__init__.cpython-310.pyc | Bin 0 -> 183 bytes .../app/__pycache__/database.cpython-310.pyc | Bin 0 -> 440 bytes .../app/__pycache__/main.cpython-310.pyc | Bin 0 -> 2849 bytes .../app/__pycache__/models.cpython-310.pyc | Bin 0 -> 574 bytes .../app/__pycache__/schemas.cpython-310.pyc | Bin 0 -> 1144 bytes fastapi-facebook-webhook/app/database.py | 8 ++ fastapi-facebook-webhook/app/main.py | 84 ++++++++++++ fastapi-facebook-webhook/app/models.py | 9 ++ fastapi-facebook-webhook/app/schemas.py | 22 +++ fastapi-facebook-webhook/docker-compose.yml | 11 ++ fastapi-facebook-webhook/requirements.txt | 6 + 14 files changed, 269 insertions(+) create mode 100644 fastapi-facebook-webhook/.env create mode 100644 fastapi-facebook-webhook/app/.gititnore create mode 100644 fastapi-facebook-webhook/app/__init__.py create mode 100644 fastapi-facebook-webhook/app/__pycache__/__init__.cpython-310.pyc create mode 100644 fastapi-facebook-webhook/app/__pycache__/database.cpython-310.pyc create mode 100644 fastapi-facebook-webhook/app/__pycache__/main.cpython-310.pyc create mode 100644 fastapi-facebook-webhook/app/__pycache__/models.cpython-310.pyc create mode 100644 fastapi-facebook-webhook/app/__pycache__/schemas.cpython-310.pyc create mode 100644 fastapi-facebook-webhook/app/database.py create mode 100644 fastapi-facebook-webhook/app/main.py create mode 100644 fastapi-facebook-webhook/app/models.py create mode 100644 fastapi-facebook-webhook/app/schemas.py create mode 100644 fastapi-facebook-webhook/docker-compose.yml create mode 100644 fastapi-facebook-webhook/requirements.txt diff --git a/fastapi-facebook-webhook/.env b/fastapi-facebook-webhook/.env new file mode 100644 index 0000000..c121d37 --- /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 0000000..bca813b --- /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 0000000..e69de29 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 GIT binary patch literal 183 zcmd1j<>g`k0)vSh=^*+sh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o11%*(xTqIJKxa z#w#&5wOGL`vnVw~!7-^QH7_$cF~%jeIJ+djAjZ*Cp`<7=GcPkQJti%&xFoS4Q#UO! xIW;LiKU=pvH7Nr~$0QaM#Kgw~6_v!t>lIYq;;_lhPbtkwwFB8%%mgG@7yxAeE?ocs literal 0 HcmV?d00001 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 GIT binary patch literal 440 zcmYk3%}N6?6or#?Ivq>fh57*9Nf-5RBNP#f6{%>uP&Z+qWG2_?XlCw^L`&WJBD(Y) z-1r)^b>%C#GE)@1ki$J8obN!e-EI;$AMZi3NeTI^%>VJ6Id;b!I*~+DMKa1Mt#+mu zXTV-+vL?M(#yz0mJyTwX`kl`k(0B=C{gv`H$*xGx|8fbWN6Yo0S?2M0dWjbGiAo{2 zld^p|ot#Z)qtol*_~PnmE5kfN84Y%J9t`L~A&gm|md6TXq2_4pVE<ru?|^#>6Vq$* zjjT%BUg=C!fD|=zz>=XU{RAxJkGfV}`0<L#n+u5Mi1#xYm4OsiM8W{?xTmuim4eu& zn3o*~_~+k<UUMNU`Q|5s@W$0M;kn4bbk9=_^X@Rxkf*T-M_}$P7U6K*wOXWknkV6{ vFjf?4|5n5hIe7oKL@0_P{423tEX&|9L;0ZK$o&d2EhjA!&=w1rPdoGn;Cz8q literal 0 HcmV?d00001 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 GIT binary patch literal 2849 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 literal 0 HcmV?d00001 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 GIT binary patch literal 574 zcmYjOJ#Q2-5cS9I-rgw@5K%yP?G-`P=oArxh=@WcP$aZk$?}~i7b{-d*pATENz3m* z{2wZ}Y2YtVF}{l+V|m`p_<818cDuV1g7)isyZVWESCWTTVe%Sxe2GpGMGYBfM+eq1 zT5@LiP<0h0$3$^e-4ay+z2RN0>Q7|W{Ki{k#`5@$jPSuJFu8uG!KfL{?MI)qv(hXJ z>7o4_dVp0R9aW@b=LEQ-iYfk$brsamJSQ+x)to?sSl?rFkE1z?MG0-@r*3tYxzJ^3 zEsd5jGp+JCf>jW(o<{@3C|8ijNn@Dg;PGNBgp~sbktae7PF<T4?+EebTAJ-i;HWUP z#TC(+#}x7-A*8h~$@07?#19gl6h?7>J|Dg6&m)A`pJ4AeJ<$O!r-zFGRxf4$7UETM zzCV0FO+jj_?W(_!sL1R63%P`ab65LcVR4DrOYeKs1tz}m>ri9+vSazt33*0aTsw4A wKbtkV^=l<<(#yb*uu-0ywDve+BYs2tuXG#jAvL%2?duO{Y2ToGjE$eNzhnuHD*ylh literal 0 HcmV?d00001 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 GIT binary patch literal 1144 zcmZ`(&2H2%5VrH<B-<_BLl1DPI3O)j`v9myZ7D)jv=>w%qDYZ9p0=i&pW3cq&+q^| z2E3B5oOlIJ%y>a8u(jl|znR4OzHhwSZ*Gnlj^i)+<!8>=Hv+38L2!W6Ru~jhe9l^4 za!&hb9<^~9bM}I%SS6pBN`QZi%LH+%1Y!|zia1j_;yhr1xKJa+qkuED@tRG?-|-7< z%6)MpEu8iW<_MBk+Pa1+t0P8mfYUz00IcMSmC-qa7!t+z8Kk&IxDt-{kr$qy`s@^} zm6uS)tyVs^aN`pPAKckg_`I$=*+O0WQC+vax}DR0T-P_Za{j9lTW9=MUCXZPopieI zEIt$K&V6NO6PbkY<L$F2)wu;@s}nrY?wn`?S38F@1D$SUbqw~}^+R>|a>p5|JKbGY z7t%U8=-mt1z^w1Dci+S83fD>wLuH#QXr<j77KXY()8-gPEay2N^N_vUqCI?cjYVV) zSwvQ$MIdlZ(5e)z624ekh1|Tb&?zuwVW822hR*rIX%yyUJNOJY<muToGZ}u#5HnL? z@QKomGaH1*6o~jAo0hwKl-t5UZ&<pDOI1M^<?q(@7^`b^{cgR0>;e0${ps`0nT3zA z_X!E`#gaRUJe%hKus6hIg*@H3Ky<BwZLAX8mA*{4q3qrKA7XVae-U_NZ&ZxENc_f| z<z+aQPI}SL-a+GhihVGP|6O0<9ViorwUqB;fr?gOAk&dB<<JVB4+|wbryCPt#tXL? o&?FNM3|%CxL0))e-T6M>U;c`o;=c;BbOFX}%qP(#orvx9Cr+8;V*mgE literal 0 HcmV?d00001 diff --git a/fastapi-facebook-webhook/app/database.py b/fastapi-facebook-webhook/app/database.py new file mode 100644 index 0000000..51361df --- /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 0000000..29d1f4b --- /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 0000000..50222b8 --- /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 0000000..e41b9e5 --- /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 0000000..3f4b0f2 --- /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 0000000..025348c --- /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 -- GitLab