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