pyfb-kit 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,126 @@
1
+ Metadata-Version: 2.3
2
+ Name: pyfb-kit
3
+ Version: 0.1.0
4
+ Summary: A wrapper for the facebook-sdk which provides an ergonomic way to manage common interactions using the facebook-sdk
5
+ Author: NovaH00
6
+ Author-email: NovaH00 <trantay2006super@gmail.com>
7
+ Requires-Dist: facebook-sdk>=3.1.0
8
+ Requires-Dist: pydantic>=2.12.5
9
+ Requires-Python: >=3.14
10
+ Description-Content-Type: text/markdown
11
+
12
+ # PyFB-kit
13
+
14
+ A Python wrapper for the Facebook Graph API that provides convenient abstractions for managing and moderating Facebook pages and comments.
15
+
16
+ ## Installation
17
+
18
+ Install the package using pip:
19
+
20
+ ```bash
21
+ pip install pyfb-kit
22
+ ```
23
+
24
+ Or using uv (recommended):
25
+
26
+ ```bash
27
+ uv add pyfb-kit
28
+ ```
29
+
30
+ ## Prerequisites
31
+
32
+ Before using this library, you need to obtain a long-lived user access token with the appropriate permissions:
33
+
34
+ 1. Create a Facebook developer application at [developers.facebook.com/apps](https://developers.facebook.com/apps/)
35
+ 2. Add the following permissions to your application:
36
+ - `pages_manage_engagement`
37
+ - `pages_read_engagement`
38
+ - `pages_read_user_content`
39
+ - `pages_show_list`
40
+ 3. Generate a short-lived user access token via the Graph API Explorer with the above permissions
41
+ 4. Exchange it for a long-lived token (valid for up to 60 days) using the following request:
42
+
43
+ ```python
44
+ import requests
45
+
46
+ params = {
47
+ "grant_type": "fb_exchange_token",
48
+ "client_id": "<YOUR_APP_ID>",
49
+ "fb_exchange_token": "<SHORT_LIVED_TOKEN>",
50
+ "client_secret": "<YOUR_APP_SECRET>",
51
+ }
52
+
53
+ response = requests.get("https://graph.facebook.com/v24.0/oauth/access_token", params=params)
54
+ long_lived_token = response.json()["access_token"]
55
+ ```
56
+
57
+ ## Usage
58
+
59
+ ### Basic Setup
60
+
61
+ ```python
62
+ from pyfb_kit import Client, Account, Post, Comment
63
+
64
+ # Initialize the client with your user access token
65
+ client = Client(user_access_token="your_long_lived_token_here")
66
+ ```
67
+
68
+ ### Managing Accounts
69
+
70
+ Retrieve all Facebook pages associated with your account:
71
+
72
+ ```python
73
+ # Get all accessible accounts/pages
74
+ accounts = client.get_accounts()
75
+
76
+ # Print account information
77
+ for account in accounts:
78
+ print(f"Page Name: {account.name}, ID: {account.id}")
79
+ ```
80
+
81
+ ### Working with Posts
82
+
83
+ Fetch posts from a specific page:
84
+
85
+ ```python
86
+ # Get posts from a specific account
87
+ first_account = accounts[0]
88
+ posts = client.get_posts(first_account)
89
+
90
+ # Print post information
91
+ for post in posts:
92
+ print(f"Post ID: {post.id}, Message: {post.message[:50]}...")
93
+ ```
94
+
95
+ ### Comment Management
96
+
97
+ Interact with comments on posts:
98
+
99
+ ```python
100
+ # Get comments from a specific post
101
+ first_post = posts[0]
102
+ comments = client.get_comments(first_account, first_post)
103
+
104
+ # Print comment information
105
+ for comment in comments:
106
+ print(f"Comment by {comment.from_info.name}: {comment.message[:50]}...")
107
+
108
+ # Post a new comment on a post
109
+ client.put_comment(first_account, first_post, "This is a new comment!")
110
+
111
+ # Reply to an existing comment
112
+ first_comment = comments[0]
113
+ client.reply_comment(first_account, first_comment, "Thanks for your comment!")
114
+ ```
115
+
116
+ ### Advanced Comment Operations
117
+
118
+ Retrieve replies to a specific comment:
119
+
120
+ ```python
121
+ # Get all replies to a specific comment
122
+ replies = client.get_comment_replies(first_account, first_comment)
123
+
124
+ for reply in replies:
125
+ print(f"Reply by {reply.from_info.name}: {reply.message[:50]}...")
126
+ ```
@@ -0,0 +1,115 @@
1
+ # PyFB-kit
2
+
3
+ A Python wrapper for the Facebook Graph API that provides convenient abstractions for managing and moderating Facebook pages and comments.
4
+
5
+ ## Installation
6
+
7
+ Install the package using pip:
8
+
9
+ ```bash
10
+ pip install pyfb-kit
11
+ ```
12
+
13
+ Or using uv (recommended):
14
+
15
+ ```bash
16
+ uv add pyfb-kit
17
+ ```
18
+
19
+ ## Prerequisites
20
+
21
+ Before using this library, you need to obtain a long-lived user access token with the appropriate permissions:
22
+
23
+ 1. Create a Facebook developer application at [developers.facebook.com/apps](https://developers.facebook.com/apps/)
24
+ 2. Add the following permissions to your application:
25
+ - `pages_manage_engagement`
26
+ - `pages_read_engagement`
27
+ - `pages_read_user_content`
28
+ - `pages_show_list`
29
+ 3. Generate a short-lived user access token via the Graph API Explorer with the above permissions
30
+ 4. Exchange it for a long-lived token (valid for up to 60 days) using the following request:
31
+
32
+ ```python
33
+ import requests
34
+
35
+ params = {
36
+ "grant_type": "fb_exchange_token",
37
+ "client_id": "<YOUR_APP_ID>",
38
+ "fb_exchange_token": "<SHORT_LIVED_TOKEN>",
39
+ "client_secret": "<YOUR_APP_SECRET>",
40
+ }
41
+
42
+ response = requests.get("https://graph.facebook.com/v24.0/oauth/access_token", params=params)
43
+ long_lived_token = response.json()["access_token"]
44
+ ```
45
+
46
+ ## Usage
47
+
48
+ ### Basic Setup
49
+
50
+ ```python
51
+ from pyfb_kit import Client, Account, Post, Comment
52
+
53
+ # Initialize the client with your user access token
54
+ client = Client(user_access_token="your_long_lived_token_here")
55
+ ```
56
+
57
+ ### Managing Accounts
58
+
59
+ Retrieve all Facebook pages associated with your account:
60
+
61
+ ```python
62
+ # Get all accessible accounts/pages
63
+ accounts = client.get_accounts()
64
+
65
+ # Print account information
66
+ for account in accounts:
67
+ print(f"Page Name: {account.name}, ID: {account.id}")
68
+ ```
69
+
70
+ ### Working with Posts
71
+
72
+ Fetch posts from a specific page:
73
+
74
+ ```python
75
+ # Get posts from a specific account
76
+ first_account = accounts[0]
77
+ posts = client.get_posts(first_account)
78
+
79
+ # Print post information
80
+ for post in posts:
81
+ print(f"Post ID: {post.id}, Message: {post.message[:50]}...")
82
+ ```
83
+
84
+ ### Comment Management
85
+
86
+ Interact with comments on posts:
87
+
88
+ ```python
89
+ # Get comments from a specific post
90
+ first_post = posts[0]
91
+ comments = client.get_comments(first_account, first_post)
92
+
93
+ # Print comment information
94
+ for comment in comments:
95
+ print(f"Comment by {comment.from_info.name}: {comment.message[:50]}...")
96
+
97
+ # Post a new comment on a post
98
+ client.put_comment(first_account, first_post, "This is a new comment!")
99
+
100
+ # Reply to an existing comment
101
+ first_comment = comments[0]
102
+ client.reply_comment(first_account, first_comment, "Thanks for your comment!")
103
+ ```
104
+
105
+ ### Advanced Comment Operations
106
+
107
+ Retrieve replies to a specific comment:
108
+
109
+ ```python
110
+ # Get all replies to a specific comment
111
+ replies = client.get_comment_replies(first_account, first_comment)
112
+
113
+ for reply in replies:
114
+ print(f"Reply by {reply.from_info.name}: {reply.message[:50]}...")
115
+ ```
@@ -0,0 +1,17 @@
1
+ [project]
2
+ name = "pyfb-kit"
3
+ version = "0.1.0"
4
+ description = "A wrapper for the facebook-sdk which provides an ergonomic way to manage common interactions using the facebook-sdk"
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "NovaH00", email = "trantay2006super@gmail.com" }
8
+ ]
9
+ requires-python = ">=3.14"
10
+ dependencies = [
11
+ "facebook-sdk>=3.1.0",
12
+ "pydantic>=2.12.5",
13
+ ]
14
+
15
+ [build-system]
16
+ requires = ["uv_build>=0.9.17,<0.10.0"]
17
+ build-backend = "uv_build"
@@ -0,0 +1,11 @@
1
+ from .client import Client, AsyncClient
2
+ from .models import Account, Post, Comment
3
+
4
+ __all__ = [
5
+ "Client",
6
+ "AsyncClient",
7
+ "Account",
8
+ "Post",
9
+ "Comment"
10
+ ]
11
+
@@ -0,0 +1,7 @@
1
+ from .sync import Client
2
+ from .asycn import AsyncClient
3
+
4
+ __all__ = [
5
+ "Client",
6
+ "AsyncClient"
7
+ ]
@@ -0,0 +1,3 @@
1
+
2
+ class AsyncClient:
3
+ pass
@@ -0,0 +1,228 @@
1
+ from typing import Any, cast
2
+ from collections.abc import Iterator
3
+
4
+ import facebook
5
+ from facebook import GraphAPI
6
+
7
+ from ..models import (
8
+ Account,
9
+ Post,
10
+ Comment
11
+ )
12
+
13
+ from ..types.graph_api import (
14
+ Response,
15
+ Data,
16
+ )
17
+
18
+ class Client:
19
+ """
20
+ Synchronous client for interacting with the Facebook Graph API.
21
+
22
+ Provides methods to manage Facebook pages, posts, and comments.
23
+ """
24
+
25
+ def __init__(self, user_access_token: str):
26
+ """
27
+ Initialize the client with a user access token.
28
+
29
+ Args:
30
+ user_access_token: A valid Facebook user access token with appropriate permissions
31
+ """
32
+ self._user_access_token: str = user_access_token
33
+
34
+ def _get_data(
35
+ self,
36
+ graph: facebook.GraphAPI,
37
+ id: str,
38
+ connection_name: str,
39
+ **kwargs: Any
40
+ ) -> Data:
41
+ """
42
+ Internal method to retrieve paginated data from the Facebook Graph API.
43
+
44
+ This method handles pagination automatically by following the cursors in the response
45
+ and concatenating all pages to produce a single data response.
46
+
47
+ Args:
48
+ graph: An initialized GraphAPI instance
49
+ id: The ID of the object to query (e.g., user ID, page ID, post ID)
50
+ connection_name: The connection name to query (e.g., "posts", "comments", "accounts")
51
+ **kwargs: Additional parameters to pass to the Graph API call
52
+
53
+ Returns:
54
+ A list of raw Facebook Graph API objects extracted from the 'data' field
55
+ """
56
+ all_data: Data = []
57
+
58
+ while True:
59
+ # Request the current page
60
+ res = graph.get_connections( # pyright: ignore
61
+ id,
62
+ connection_name,
63
+ **kwargs
64
+ )
65
+ res = cast(Response, res)
66
+
67
+ current_data: Data = res.get("data") # pyright: ignore
68
+
69
+ if current_data:
70
+ all_data.extend(current_data)
71
+
72
+ # Check for the next page cursor ('after')
73
+ # Facebook Structure: {'paging': {'cursors': {'after': '...'}}}
74
+ try:
75
+ paging = res.get("paging", None)
76
+ if not paging: break
77
+
78
+ cursors = paging.get("cursors")
79
+ if not cursors: break
80
+
81
+ after = cursors.get("after")
82
+ if not after: break
83
+
84
+ # Update kwargs with the 'after' cursor for the next iteration
85
+ kwargs["after"] = after
86
+
87
+ except (AttributeError, TypeError):
88
+ # If the response structure is unexpected, stop pagination
89
+ break
90
+
91
+ return all_data
92
+
93
+ def get_accounts(self) -> list[Account]:
94
+ """
95
+ Retrieve all Facebook accounts/pages associated with the authenticated user.
96
+
97
+ This method fetches all Facebook pages that the user has access to, along with
98
+ their access tokens and other metadata.
99
+
100
+ Returns:
101
+ A list of Account objects representing the Facebook pages accessible to the user
102
+ """
103
+ accounts: Data = self._get_data(
104
+ graph=GraphAPI(self._user_access_token),
105
+ id="me",
106
+ connection_name="accounts"
107
+ )
108
+
109
+ return [
110
+ Account.model_validate(acc)
111
+ for acc in accounts
112
+ ]
113
+
114
+ def get_posts(self, account: Account) -> list[Post]:
115
+ """
116
+ Retrieve all posts from a specific Facebook page/account.
117
+
118
+ This method fetches all posts from the given Facebook page, including their
119
+ content, creation time, and attachments.
120
+
121
+ Args:
122
+ account: Account object containing the access token and ID for the Facebook page
123
+
124
+ Returns:
125
+ A list of Post objects representing the posts on the Facebook page
126
+ """
127
+ posts: Data = self._get_data(
128
+ graph=GraphAPI(account.access_token),
129
+ id=account.id,
130
+ connection_name="posts",
131
+ fields="id,message,created_time,attachments{media,type,subattachments}",
132
+ )
133
+
134
+ return [
135
+ Post.model_validate(p)
136
+ for p in posts
137
+ ]
138
+
139
+ def get_comments(self, account: Account, post: Post) -> list[Comment]:
140
+ """
141
+ Retrieve all comments for a given post using the account's access token.
142
+
143
+ Args:
144
+ account: Account object containing the access token for the Facebook page
145
+ post: Post object containing the post information
146
+
147
+ Returns:
148
+ A list of Comment objects representing the comments on the post
149
+ """
150
+ comments: Data = self._get_data(
151
+ graph=GraphAPI(account.access_token),
152
+ id=post.id,
153
+ connection_name="comments",
154
+ fields="id,from,message,created_time,like_count,parent"
155
+ )
156
+
157
+ return [
158
+ Comment.model_validate(comment)
159
+ for comment in comments
160
+ ]
161
+
162
+ def put_comment(self, account: Account, post: Post, message: str) -> None:
163
+ """
164
+ Post a direct comment on a post using the account's access token.
165
+
166
+ Args:
167
+ account: Account object containing the access token for the Facebook page
168
+ post: Post object containing the post information
169
+ message: The message content for the comment
170
+
171
+ Returns:
172
+ None
173
+ """
174
+ graph = GraphAPI(account.access_token)
175
+
176
+ # Use the Facebook Graph API to post a comment on the specific post
177
+ _ = graph.put_object( # pyright: ignore
178
+ parent_object=post.id,
179
+ connection_name="comments",
180
+ message=message
181
+ )
182
+
183
+ def reply_comment(self, account: Account, comment: Comment, reply_message: str) -> None:
184
+ """
185
+ Reply to a specific comment using the account's access token.
186
+ This creates a reply to the given comment.
187
+
188
+ Args:
189
+ account: Account object containing the access token for the Facebook page
190
+ comment: Comment object representing the comment to reply to
191
+ reply_message: The message content for the reply
192
+
193
+ Returns:
194
+ None
195
+ """
196
+ graph = GraphAPI(account.access_token)
197
+
198
+ # Use the Facebook Graph API to reply to the specific comment
199
+ _ = graph.put_object( # pyright: ignore
200
+ parent_object=comment.id,
201
+ connection_name="comments",
202
+ message=reply_message
203
+ )
204
+
205
+ def get_comment_replies(self, account: Account, comment: Comment) -> list[Comment]:
206
+ """
207
+ Retrieve all replies to a specific comment using the account's access token.
208
+
209
+ Args:
210
+ account: Account object containing the access token for the Facebook page
211
+ comment: Comment object representing the parent comment whose replies are to be retrieved
212
+
213
+ Returns:
214
+ A list of Comment objects representing the replies to the given comment
215
+ """
216
+ replies: Data = self._get_data(
217
+ graph=GraphAPI(account.access_token),
218
+ id=comment.id,
219
+ connection_name="comments",
220
+ fields="id,from,message,created_time,like_count,parent"
221
+ )
222
+
223
+ return [
224
+ Comment.model_validate(reply)
225
+ for reply in replies
226
+ ]
227
+
228
+ # TODO: Add hide/unhide comments methods
@@ -0,0 +1,10 @@
1
+ from .accounts import Account
2
+ from .posts import Post
3
+ from .comments import Comment
4
+
5
+ __all__ = [
6
+ "Account",
7
+ "Post",
8
+ "Comment"
9
+ ]
10
+
@@ -0,0 +1,9 @@
1
+ from pydantic import BaseModel, Field
2
+
3
+ class Account(BaseModel):
4
+ access_token: str = Field(description="The access token needed to do operation using this account")
5
+ id: str = Field(description="The ID of the account")
6
+ name: str = Field(description="The name of the account")
7
+ tasks: list[str] = Field(description="The roles of CURRENT USER for this account")
8
+
9
+
@@ -0,0 +1,48 @@
1
+ from typing import Any
2
+ from datetime import datetime
3
+ from pydantic import BaseModel, Field, model_validator
4
+
5
+
6
+ class From(BaseModel):
7
+ id: str = Field(..., description="ID of the user who made the comment")
8
+ name: str = Field(..., description="Name of the user who made the comment")
9
+
10
+
11
+ class Comment(BaseModel):
12
+ """
13
+ Normalized DTO for a Facebook comment.
14
+
15
+ IMPORTANT:
16
+ This model expects the raw input to include comment data from the
17
+ Facebook Graph API. When querying comments, you MUST request the following
18
+ fields for validation and normalization to work correctly:
19
+
20
+ ...get_connections(
21
+ post_id,
22
+ "comments",
23
+ fields="id,from,message,created_time,like_count,parent"
24
+ )
25
+
26
+ If required fields are missing from the response, validation will fail.
27
+ """
28
+ id: str = Field(..., description="Unique identifier of the Facebook comment")
29
+ message: str = Field(..., description="Text content of the comment")
30
+ created_time: datetime = Field(..., description="Timestamp when the comment was created (ISO-8601)")
31
+ from_info: From = Field(..., alias="from", description="Information about the user who made the comment")
32
+ like_count: int = Field(default=0, description="Number of likes on the comment")
33
+ parent_id: str | None = Field(None, description="ID of the parent comment if this is a reply, otherwise None")
34
+
35
+ @model_validator(mode="before")
36
+ @classmethod
37
+ def extract_facebook_payload(cls, raw: dict[str, Any]):
38
+ return {
39
+ "id": raw["id"],
40
+ "message": raw["message"],
41
+ "created_time": raw["created_time"],
42
+ "from": {
43
+ "id": raw["from"]["id"],
44
+ "name": raw["from"]["name"]
45
+ },
46
+ "like_count": raw.get("like_count", 0),
47
+ "parent_id": raw.get("parent", {}).get("id") if raw.get("parent") else None
48
+ }
@@ -0,0 +1,66 @@
1
+ from typing import Any
2
+ from datetime import datetime
3
+ from pydantic import BaseModel, Field, model_validator
4
+
5
+
6
+ class Image(BaseModel):
7
+ src: str = Field(..., description="Direct URL to the image hosted by Facebook")
8
+ width: int = Field(..., description="Width of the image in pixels")
9
+ height: int = Field(..., description="Height of the image in pixels")
10
+
11
+
12
+ class Attachments(BaseModel):
13
+ images: list[Image] | None = Field(None, description="List of images attached to the post, if any")
14
+ # TODO: Add other attachment types here (videos, links, etc.)
15
+
16
+ class Post(BaseModel):
17
+ """
18
+ Normalized DTO for a Facebook Page post.
19
+
20
+ IMPORTANT:
21
+ This model expects the raw input to include attachment data from the
22
+ Facebook Graph API. When querying posts, you MUST request the following
23
+ fields for validation and normalization to work correctly:
24
+
25
+ ...get_connections(
26
+ account_id,
27
+ "posts",
28
+ fields="id,message,created_time,attachments{media,type,subattachments}"
29
+ )
30
+
31
+ If `attachments` or `attachments.media.image` are missing from the
32
+ response, `attachments.images` will be set to None.
33
+ """
34
+ id: str = Field(..., description="Unique identifier of the Facebook post")
35
+ created_time: datetime = Field(..., description="Timestamp when the post was created (ISO-8601)")
36
+ message: str | None = Field(None, description="Text content of the post")
37
+ attachments: Attachments | None = Field(None, description="Normalized attachments associated with the post")
38
+
39
+ @model_validator(mode="before")
40
+ @classmethod
41
+ def extract_facebook_payload(cls, raw: dict[str, Any]):
42
+ images: list[Image] = []
43
+
44
+ for att in raw.get("attachments", {}).get("data", []):
45
+ if att.get("type") != "photo":
46
+ continue
47
+
48
+ image = att.get("media", {}).get("image")
49
+ if not image:
50
+ continue
51
+
52
+ images.append(
53
+ Image(
54
+ src=image["src"],
55
+ width=image["width"],
56
+ height=image["height"],
57
+ )
58
+ )
59
+
60
+ return {
61
+ "id": raw["id"],
62
+ "created_time": raw["created_time"],
63
+ "message": raw.get("message"),
64
+ "attachments": {"images": images} if images else None,
65
+ }
66
+
File without changes
File without changes
@@ -0,0 +1,12 @@
1
+ from typing import Annotated, Any
2
+
3
+ type Response = Annotated[
4
+ dict[str, Any],
5
+ "Raw Facebook Graph API response object returned by get_connections()."
6
+ ]
7
+
8
+ type Data = Annotated[
9
+ list[dict[str, Any]],
10
+ "List of raw Facebook Graph API objects extracted from the 'data' field."
11
+ ]
12
+