Skip to content

Commit

Permalink
Merge pull request #204 from MerleLiuKun/feat-product-tagging
Browse files Browse the repository at this point in the history
Feat product tagging
  • Loading branch information
MerleLiuKun authored Jul 22, 2022
2 parents cbf28c4 + 1176e8a commit c39c431
Show file tree
Hide file tree
Showing 9 changed files with 313 additions and 9 deletions.
23 changes: 22 additions & 1 deletion pyfacebook/api/instagram_business/resource/media.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
IgBusCommentResponse,
IgBusMediaChildren,
IgBusInsightsResponse,
IgBusProductTagsResponse,
)
from pyfacebook.utils.params_utils import enf_comma_separated

Expand Down Expand Up @@ -154,7 +155,7 @@ def get_insights(
:param media_id: ID for the media.
:param metric: A comma-separated list of Metrics you want returned.
You can also pass this with an id list, tuple.
:param return_json: Set to false will return a dataclass for IgBusPublishLimitResponse.
:param return_json: Set to false will return a dataclass for IgBusInsightsResponse.
Or return json data. Default is false.
:return: Media insights response information.
"""
Expand All @@ -168,3 +169,23 @@ def get_insights(
return data
else:
return IgBusInsightsResponse.new_from_json_dict(data)

def get_product_tags(
self, media_id: str, return_json: bool = False
) -> Union[IgBusProductTagsResponse, dict]:
"""
Get a collection of product tags on an IG Media.
:param media_id: ID for the media.
:param return_json: Set to false will return a dataclass for IgBusProductTagsResponse.
Or return json data. Default is false.
:return: Media product tags
"""

data = self.client.get_connection(
object_id=media_id,
connection="product_tags",
)
if return_json:
return data
else:
return IgBusProductTagsResponse.new_from_json_dict(data)
98 changes: 90 additions & 8 deletions pyfacebook/api/instagram_business/resource/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
IgBusMentionedCommentResponse,
IgBusMentionedMediaResponse,
IgBusHashtagsResponse,
IgBusCatalogsResponse,
IgBusProductsResponse,
IgBusProductAppealsResponse,
)
from pyfacebook.utils.params_utils import enf_comma_separated

Expand Down Expand Up @@ -217,7 +220,7 @@ def get_media(
until: Optional[str] = None,
count: Optional[int] = 10,
limit: Optional[int] = 10,
return_json=False,
return_json: bool = False,
) -> Union[IgBusMediaResponse, dict]:
"""
Get user's media. This only can return max 10k medias.
Expand Down Expand Up @@ -258,7 +261,7 @@ def get_live_media(
until: Optional[str] = None,
count: Optional[int] = 10,
limit: Optional[int] = 10,
return_json=False,
return_json: bool = False,
) -> Union[IgBusMediaResponse, dict]:
"""
Represents a collection of live video IG Media on an IG User.
Expand Down Expand Up @@ -297,7 +300,7 @@ def get_mentioned_comment(
self,
comment_id: str,
fields: Optional[Union[str, list, tuple]] = None,
return_json=False,
return_json: bool = False,
) -> Union[IgBusMentionedCommentResponse, dict]:
"""
Get data on an IG Comment in which user has been @mentioned by another Instagram user
Expand Down Expand Up @@ -328,7 +331,7 @@ def get_mentioned_media(
self,
media_id: str,
fields: Optional[Union[str, list, tuple]] = None,
return_json=False,
return_json: bool = False,
) -> Union[IgBusMentionedMediaResponse, dict]:
"""
Get data on an IG Media in which user has been @mentioned in a caption by another Instagram user.
Expand Down Expand Up @@ -357,7 +360,7 @@ def get_mentioned_media(
def get_hashtag_search(
self,
q: str,
return_json=False,
return_json: bool = False,
) -> Union[IgBusHashtagsResponse, dict]:
"""
Get ID for hashtag.
Expand Down Expand Up @@ -385,7 +388,7 @@ def get_recently_searched_hashtags(
fields: Optional[Union[str, list, tuple]] = None,
count: Optional[int] = 25,
limit: Optional[int] = 25,
return_json=False,
return_json: bool = False,
) -> Union[IgBusHashtagsResponse, dict]:
"""
Get the IG Hashtags that user has searched for within the last 7 days.
Expand Down Expand Up @@ -419,7 +422,7 @@ def get_stories(
fields: Optional[Union[str, list, tuple]] = None,
count: Optional[int] = 10,
limit: Optional[int] = 10,
return_json=False,
return_json: bool = False,
) -> Union[IgBusMediaResponse, dict]:
"""
Get list of story IG Media objects on user.
Expand Down Expand Up @@ -454,7 +457,7 @@ def get_tagged_media(
fields: Optional[Union[str, list, tuple]] = None,
count: Optional[int] = 25,
limit: Optional[int] = 25,
return_json=False,
return_json: bool = False,
) -> Union[IgBusMediaResponse, dict]:
"""
Get list of Media objects in which user has been tagged by another Instagram user.
Expand Down Expand Up @@ -483,3 +486,82 @@ def get_tagged_media(
return data
else:
return IgBusMediaResponse.new_from_json_dict(data)

def get_available_catalogs(
self, return_json: bool = False
) -> Union[IgBusCatalogsResponse, dict]:
"""
Get the product catalog in an IG User's Instagram Shop.
:param: return_json: Set to false will return a dataclass for IgBusCatalogsResponse.
Or return json data. Default is false.
:return: catalog data
"""

data = self.client.get_connection(
object_id=self.client.instagram_business_id,
connection="available_catalogs",
)
if return_json:
return data
else:
return IgBusCatalogsResponse.new_from_json_dict(data)

def get_catalog_product_search(
self,
catalog_id: str,
q: Optional[str] = None,
count: Optional[int] = 10,
limit: int = 10,
return_json: bool = False,
) -> Union[IgBusProductsResponse, dict]:
"""
Get a collection of products that match a given search string within the targeted IG User's Instagram Shop catalog.
:param catalog_id: ID of catalog to search.
:param q: A string to search for in each product's name or SKU number (SKU numbers can be added in the Content ID column in the catalog management interface).
If no string is specified, all tag-eligible products will be returned.
:param count: The count will retrieve objects. Default is None will get all data.
:param limit: Each request retrieve objects count.
For most connections should no more than 100. Default is None will use api default limit.
:param return_json: Set to false will return a dataclass for IgBusProductsResponse.
Or return json data. Default is false.
:return: Catalog products data.
"""

data = self.client.get_full_connections(
object_id=self.client.instagram_business_id,
connection="catalog_product_search",
catalog_id=catalog_id,
q=q,
count=count,
limit=limit,
)
if return_json:
return data
else:
return IgBusProductsResponse.new_from_json_dict(data)

def get_product_appeal(
self,
product_id: str,
return_json: bool = False,
) -> Union[IgBusProductAppealsResponse, dict]:
"""
Get appeal status of a rejected product.
:param product_id: Product ID.
:param return_json: Set to false will return a dataclass for IgBusProductAppealsResponse.
Or return json data. Default is false.
:return: Product appeals data.
"""

data = self.client.get_connection(
object_id=self.client.instagram_business_id,
connection="product_appeal",
product_id=product_id,
)
if return_json:
return data
else:
return IgBusProductAppealsResponse.new_from_json_dict(data)
118 changes: 118 additions & 0 deletions pyfacebook/models/ig_business_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class IgBusUser(BaseModel):
media_count: Optional[int] = field()
name: Optional[int] = field()
profile_picture_url: Optional[str] = field()
shopping_product_tag_eligibility: Optional[bool] = field()
username: Optional[str] = field(repr=True)
website: Optional[str] = field()

Expand Down Expand Up @@ -314,3 +315,120 @@ class IgBusHashtagsResponse(BaseModel):

data: List[IgBusHashtag] = field(repr=True)
paging: Optional[Paging] = field(repr=True)


@dataclass
class IgBusCatalog(BaseModel):
"""
A class representing the catalog.
Refer: https://developers.facebook.com/docs/instagram-api/reference/ig-user/available_catalogs
"""

catalog_id: Optional[str] = field(repr=True)
catalog_name: Optional[str] = field(repr=True)
shop_name: Optional[str] = field(repr=True)
product_count: Optional[int] = field(repr=True)


@dataclass
class IgBusCatalogsResponse(BaseModel):
"""
A class representing the catalog list response.
Refer: https://developers.facebook.com/docs/instagram-api/reference/ig-user/available_catalogs
"""

data: List[IgBusCatalog] = field(repr=True)


@dataclass
class IgBusProductVariant(BaseModel):
product_id: Optional[int] = field(repr=True)
variant_name: Optional[str] = field(repr=True)


@dataclass
class IgBusProduct(BaseModel):
"""
A class representing the catalog product.
Refer: https://developers.facebook.com/docs/instagram-api/reference/ig-user/catalog_product_search
"""

product_id: Optional[int] = field(repr=True)
merchant_id: Optional[int] = field(repr=True)
product_name: Optional[str] = field(repr=True)
image_url: Optional[str] = field()
retailer_id: Optional[str] = field()
review_status: Optional[str] = field()
is_checkout_flow: Optional[bool] = field()
product_variants: Optional[List[IgBusProductVariant]] = field()


@dataclass
class IgBusProductsResponse(BaseModel):
"""
A class representing the catalog product list response.
Refer: https://developers.facebook.com/docs/instagram-api/reference/ig-user/catalog_product_search
"""

data: List[IgBusProduct] = field(repr=True)
paging: Optional[Paging] = field(repr=True)


@dataclass
class IgBusProductTag(BaseModel):
"""
A class representing the product tag.
Refer: https://developers.facebook.com/docs/instagram-api/reference/ig-media/product_tags#ig-media-product-tags
"""

product_id: Optional[int] = field(repr=True)
merchant_id: Optional[int] = field(repr=True)
name: Optional[str] = field(repr=True)
price_string: Optional[str] = field()
image_url: Optional[str] = field()
review_status: Optional[str] = field()
is_checkout: Optional[bool] = field()
stripped_price_string: Optional[str] = field()
string_sale_price_string: Optional[str] = field()
x: Optional[float] = field()
y: Optional[float] = field()


@dataclass
class IgBusProductTagsResponse(BaseModel):
"""
A class representing the product tag list response.
Refer: https://developers.facebook.com/docs/instagram-api/reference/ig-media/product_tags#ig-media-product-tags
"""

data: List[IgBusProductTag] = field(repr=True)


@dataclass
class IgBusProductAppeal(BaseModel):
"""
A class representing the product appeal.
Refer: https://developers.facebook.com/docs/instagram-api/reference/ig-user/product_appeal
"""

eligible_for_appeal: Optional[bool] = field(repr=True)
product_id: Optional[int] = field(repr=True)
review_status: Optional[str] = field(repr=True)


@dataclass
class IgBusProductAppealsResponse(BaseModel):
"""
A class representing the product appeal list
Refer: https://developers.facebook.com/docs/instagram-api/reference/ig-user/product_appeal#response-2
"""

data: List[IgBusProductAppeal] = field(repr=True)
1 change: 1 addition & 0 deletions testdata/instagram/apidata/medias/product_tags.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"data":[{"product_id":3231775643511089,"merchant_id":90010177253934,"name":"Gummy Bears","price_string":"$3.50","image_url":"https://scont...","review_status":"approved","is_checkout":true,"stripped_price_string":"$3.50","stripped_sale_price_string":"$3","x":0.5,"y":0.80000001192093}]}
1 change: 1 addition & 0 deletions testdata/instagram/apidata/users/available_catalogs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"data":[{"catalog_id":"960179311066902","catalog_name":"Jay's Favorite Snacks","shop_name":"Jay's Bespoke","product_count":11}]}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"data":[{"product_id":3231775643511089,"merchant_id":90010177253934,"product_name":"Gummy Wombats","image_url":"https://scont...","retailer_id":"oh59p9vzei","review_status":"approved","is_checkout_flow":true,"product_variants":[{"product_id":5209223099160494},{"product_id":7478222675582505,"variant_name":"Green Gummy Wombats"}]}]}
1 change: 1 addition & 0 deletions testdata/instagram/apidata/users/product_appeal.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"data":[{"product_id":4029274203846188,"review_status":"approved","eligible_for_appeal":false}]}
19 changes: 19 additions & 0 deletions tests/instagram_business/test_media.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,22 @@ def test_get_insights(helpers, api):
return_json=True,
)
assert insights_json["data"][0]["name"] == "impressions"


def test_get_product_tags(helpers, api):
media_id = "90010778325754"
with responses.RequestsMock() as m:
m.add(
method=responses.GET,
url=f"https://graph.facebook.com/{api.version}/{media_id}/product_tags",
json=helpers.load_json(
"testdata/instagram/apidata/medias/product_tags.json"
),
)

tags = api.media.get_product_tags(media_id=media_id)
assert len(tags.data) == 1
assert tags.data[0].product_id == 3231775643511089

tags_json = api.media.get_product_tags(media_id=media_id, return_json=True)
assert tags_json["data"][0]["review_status"] == "approved"
Loading

0 comments on commit c39c431

Please sign in to comment.