Spaces:
Sleeping
Sleeping
from typing import Any, Dict, List, Optional | |
from google.auth.credentials import Credentials | |
from google.oauth2 import service_account | |
from googleapiclient.discovery import build | |
from pydantic import Field, SecretStr, PrivateAttr | |
from pydantic_settings import BaseSettings | |
from obsei.payload import TextPayload | |
from obsei.source.base_source import BaseSource, BaseSourceConfig | |
class GoogleCredInfo(BaseSettings): | |
# Currently only service_account_file type credential supported | |
# Refer: https://developers.google.com/identity/protocols/oauth2/service-account | |
service_cred_file: Optional[str] = Field(None, env="google_service_cred_file") | |
developer_key: Optional[SecretStr] = Field(None, env="google_developer_key") | |
scopes: List[str] = ["https://www.googleapis.com/auth/androidpublisher"] | |
class PlayStoreConfig(BaseSourceConfig): | |
_credentials: Credentials = PrivateAttr() | |
TYPE: str = "PlayStore" | |
package_name: str | |
start_index: Optional[int] = None | |
max_results: int = 10 | |
num_retries: int = 1 | |
with_quota_project_id: Optional[str] = None | |
with_subject: Optional[str] = None | |
cred_info: Optional[GoogleCredInfo] = Field(None) | |
def __init__(self, **values: Any): | |
super().__init__(**values) | |
self.cred_info = self.cred_info or GoogleCredInfo() | |
if self.cred_info.service_cred_file is None or self.cred_info.developer_key is None: | |
raise ValueError("`service_cred_file` and `developer_key` can't be empty") | |
self._credentials = service_account.Credentials.from_service_account_file( | |
filename=self.cred_info.service_cred_file, scopes=self.cred_info.scopes | |
) | |
if self.with_quota_project_id is not None: | |
self._credentials = self._credentials.with_quota_project(self.with_quota_project_id) | |
if self.with_subject is not None: | |
self._credentials = self._credentials.with_subject(self.with_subject) | |
def get_google_credentials(self) -> Credentials: | |
return self._credentials | |
def get_developer_key(self) -> str: | |
if self.cred_info is None or self.cred_info.developer_key is None: | |
raise ValueError("`developer_key` can't be empty") | |
return self.cred_info.developer_key.get_secret_value() | |
class PlayStoreSource(BaseSource): | |
NAME: str = "PlayStore" | |
def lookup(self, config: PlayStoreConfig, **kwargs: Any) -> List[TextPayload]: # type: ignore[override] | |
source_responses: List[TextPayload] = [] | |
# Refer https://github.com/googleapis/google-api-python-client/blob/master/docs/start.md | |
with build( | |
serviceName="androidpublisher", | |
version="v3", | |
credentials=config.get_google_credentials(), | |
developerKey=config.get_developer_key(), | |
) as service: | |
reviews = service.reviews() | |
pagination_token: Optional[str] = None | |
# Get data from state | |
id: str = kwargs.get("id", None) | |
state: Optional[Dict[str, Any]] = ( | |
None | |
if id is None or self.store is None | |
else self.store.get_source_state(id) | |
) | |
start_index: Optional[int] = ( | |
config.start_index or None | |
if state is None | |
else state.get("start_index", None) | |
) | |
update_state: bool = True if id else False | |
state = state or dict() | |
review_id = start_index | |
while True: | |
# Refer following link - | |
# https://googleapis.github.io/google-api-python-client/docs/dyn/androidpublisher_v3.reviews.html#list | |
responses = reviews.list( | |
package_name=config.package_name, | |
max_results=config.max_results, | |
start_index=start_index, | |
token=pagination_token, | |
) | |
if "reviews" in responses: | |
reviews = responses["responses"] | |
for review in reviews: | |
if "comments" not in review: | |
continue | |
review_id = review["reviewId"] | |
# Currently only one user comment is supported | |
text = review["comments"][0]["userComment"]["text"] | |
source_responses.append( | |
TextPayload( | |
processed_text=text, meta=review, source_name=self.NAME | |
) | |
) | |
pagination_token = None | |
if "tokenPagination" in responses: | |
if "nextPageToken" in responses["tokenPagination"]: | |
pagination_token = responses["tokenPagination"]["nextPageToken"] | |
if pagination_token is None: | |
break | |
if update_state and self.store is not None: | |
state["start_index"] = review_id | |
self.store.update_source_state(workflow_id=id, state=state) | |
return source_responses | |