gameflip-api 1.0.1__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,82 @@
1
+ Metadata-Version: 2.1
2
+ Name: gameflip-api
3
+ Version: 1.0.1
4
+ Summary: Uma API wrapper em Python para utilização com a plataforma Gameflip.
5
+ Author-email: Gustavo Pedroso Bernardes <gpedrosobernardes@gmail.com>
6
+ Requires-Python: >=3.8
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: validators
9
+ Requires-Dist: pydantic
10
+ Requires-Dist: pyotp
11
+ Requires-Dist: requests
12
+
13
+ # Gameflip API
14
+
15
+ Uma **API wrapper em Python** para utilização com a plataforma Gameflip.
16
+ Facilita a integração via script, para automatizar operações, consultar dados e interagir com a Gameflip de forma programática.
17
+
18
+ ## 🧰 Funcionalidades
19
+
20
+ * Conexão autenticada com a Gameflip via chave e segredo (`API_KEY`, `API_SECRET`)
21
+ * Métodos para executar operações comuns (por exemplo: listagem de produtos, criação de ofertas, consulta de histórico)
22
+ * Interface simples em Python para agilizar automações
23
+ * Código open-source, fácil de estender para necessidades específicas
24
+
25
+ ## 🚀 Começando
26
+
27
+ ### Pré-requisitos
28
+
29
+ * Python (versão compatível — idealmente 3.8+)
30
+ * Conta na Gameflip e acesso à API (chave + segredo)
31
+ * Variáveis de ambiente configuradas:
32
+
33
+ ```bash
34
+ export GFAPI_KEY=<sua_chave_aqui>
35
+ export GFAPI_SECRET=<seu_segredo_aqui>
36
+ ```
37
+
38
+ (No Windows: `set GFAPI_KEY=<…>`, `set GFAPI_SECRET=<…>`)
39
+
40
+ ### Instalação
41
+
42
+ ```bash
43
+ pip install gameflip-api
44
+ ```
45
+
46
+ ### Uso básico
47
+
48
+ Exemplo no arquivo `example.py` (ajuste conforme a sua necessidade):
49
+
50
+ ```python
51
+ import os
52
+ from pprint import pprint
53
+ from dotenv import load_dotenv
54
+ from gameflip_api.api import GameflipAPI
55
+
56
+ load_dotenv()
57
+
58
+ print("Listing results:")
59
+
60
+ listing_response = GameflipAPI.listing_search(digital=True, limit=1)
61
+ pprint(listing_response.json())
62
+
63
+ gameflip_api = GameflipAPI(os.getenv('GFAPI_KEY'), os.getenv('GFAPI_SECRET'))
64
+
65
+ print("My profile info:")
66
+
67
+ pprint(gameflip_api.profile().json())
68
+ ```
69
+
70
+ ## 📦 Estrutura do Projeto
71
+
72
+ * `src/gameflip_api/` — código-fonte da biblioteca
73
+ * `example.py` — script de demonstração de uso
74
+ * `test.py` — arquivo para testes rápidos
75
+ * `requirements.txt` — dependências do Python
76
+
77
+ ## 🤝 Contato
78
+
79
+ Se tiver dúvidas, sugestões ou quiser colaborar:
80
+
81
+ * Crie uma *issue* no próprio repositório
82
+ * Envie um pull request com descrições claras das alterações
@@ -0,0 +1,70 @@
1
+ # Gameflip API
2
+
3
+ Uma **API wrapper em Python** para utilização com a plataforma Gameflip.
4
+ Facilita a integração via script, para automatizar operações, consultar dados e interagir com a Gameflip de forma programática.
5
+
6
+ ## 🧰 Funcionalidades
7
+
8
+ * Conexão autenticada com a Gameflip via chave e segredo (`API_KEY`, `API_SECRET`)
9
+ * Métodos para executar operações comuns (por exemplo: listagem de produtos, criação de ofertas, consulta de histórico)
10
+ * Interface simples em Python para agilizar automações
11
+ * Código open-source, fácil de estender para necessidades específicas
12
+
13
+ ## 🚀 Começando
14
+
15
+ ### Pré-requisitos
16
+
17
+ * Python (versão compatível — idealmente 3.8+)
18
+ * Conta na Gameflip e acesso à API (chave + segredo)
19
+ * Variáveis de ambiente configuradas:
20
+
21
+ ```bash
22
+ export GFAPI_KEY=<sua_chave_aqui>
23
+ export GFAPI_SECRET=<seu_segredo_aqui>
24
+ ```
25
+
26
+ (No Windows: `set GFAPI_KEY=<…>`, `set GFAPI_SECRET=<…>`)
27
+
28
+ ### Instalação
29
+
30
+ ```bash
31
+ pip install gameflip-api
32
+ ```
33
+
34
+ ### Uso básico
35
+
36
+ Exemplo no arquivo `example.py` (ajuste conforme a sua necessidade):
37
+
38
+ ```python
39
+ import os
40
+ from pprint import pprint
41
+ from dotenv import load_dotenv
42
+ from gameflip_api.api import GameflipAPI
43
+
44
+ load_dotenv()
45
+
46
+ print("Listing results:")
47
+
48
+ listing_response = GameflipAPI.listing_search(digital=True, limit=1)
49
+ pprint(listing_response.json())
50
+
51
+ gameflip_api = GameflipAPI(os.getenv('GFAPI_KEY'), os.getenv('GFAPI_SECRET'))
52
+
53
+ print("My profile info:")
54
+
55
+ pprint(gameflip_api.profile().json())
56
+ ```
57
+
58
+ ## 📦 Estrutura do Projeto
59
+
60
+ * `src/gameflip_api/` — código-fonte da biblioteca
61
+ * `example.py` — script de demonstração de uso
62
+ * `test.py` — arquivo para testes rápidos
63
+ * `requirements.txt` — dependências do Python
64
+
65
+ ## 🤝 Contato
66
+
67
+ Se tiver dúvidas, sugestões ou quiser colaborar:
68
+
69
+ * Crie uma *issue* no próprio repositório
70
+ * Envie um pull request com descrições claras das alterações
@@ -0,0 +1,15 @@
1
+ [project]
2
+ name = "gameflip-api"
3
+ version = "1.0.1"
4
+ description = "Uma API wrapper em Python para utilização com a plataforma Gameflip."
5
+ authors = [{ name="Gustavo Pedroso Bernardes", email="gpedrosobernardes@gmail.com" }]
6
+ readme = "README.md"
7
+ requires-python = ">=3.8"
8
+ dependencies = ["validators", "pydantic", "pyotp", "requests"]
9
+
10
+ [build-system]
11
+ requires = ["setuptools", "wheel"]
12
+ build-backend = "setuptools.build_meta"
13
+
14
+ [tool.pydantic-pycharm-plugin.parsable-types]
15
+ "src.gameflip_api.types.AwsCognitoUUID" = ["str"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
File without changes
@@ -0,0 +1,136 @@
1
+ from pathlib import Path
2
+
3
+ import requests
4
+ import pyotp
5
+
6
+ from gameflip_api.enums import ListingOps, ListingPhotoStatus
7
+ from gameflip_api.params import GameflipAPIParams, UUIDParam, ListingPostParams, Op, ExchangeParams, ExchangePostParams, \
8
+ OwnerParam, ListingSearchParams
9
+ import validators
10
+
11
+
12
+ class GameflipAPI:
13
+ __api = 'https://production-gameflip.fingershock.com/api/v1'
14
+
15
+ def __init__(self, api_key, secret):
16
+ GameflipAPIParams(api_key=api_key, secret=secret)
17
+ self.__api_key = api_key
18
+ self.__secret = secret
19
+ self.__totp = pyotp.TOTP(secret)
20
+
21
+ def __get_auth_header(self):
22
+ return {"Authorization": f"GFAPI {self.__api_key}:{self.__totp.now()}"}
23
+
24
+ def profile(self, owner = 'me'):
25
+ if owner != 'me':
26
+ # noinspection PyTypeChecker
27
+ OwnerParam(owner=owner)
28
+ return requests.get(f"{self.__api}/account/{owner}/profile", headers=self.__get_auth_header())
29
+
30
+ def wallet_history(self):
31
+ return requests.get(f"{self.__api}/account/me/wallet_history", headers=self.__get_auth_header())
32
+
33
+ @staticmethod
34
+ def get_rldata_items():
35
+ return requests.get("https://gameflip.com/api/gameitem/inventory/812872018935")
36
+
37
+ @classmethod
38
+ def listing_search(cls, params = None, **kwargs):
39
+ if params is None:
40
+ params = ListingSearchParams(**kwargs)
41
+ elif not isinstance(params, ListingSearchParams):
42
+ raise TypeError("params must be of type ListingPostParams.")
43
+ return requests.get(f"{cls.__api}/listing", params.model_dump(exclude_none=True))
44
+
45
+ def my_listing_search(self, params = None, **kwargs):
46
+ if params is None:
47
+ params = ListingSearchParams(**kwargs)
48
+ elif not isinstance(params, ListingSearchParams):
49
+ raise TypeError("params must be of type ListingPostParams.")
50
+ return requests.get(f"{self.__api}/listing", params.model_dump(exclude_none=True),
51
+ headers=self.__get_auth_header())
52
+
53
+ @classmethod
54
+ def listing_of(cls, uuid):
55
+ UUIDParam(uuid=uuid)
56
+ return requests.get(f"{cls.__api}/listing/{uuid}")
57
+
58
+ def my_listing_of(self, uuid):
59
+ UUIDParam(uuid=uuid)
60
+ return requests.get(f"{self.__api}/listing/{uuid}", headers=self.__get_auth_header())
61
+
62
+ def listing_post(self, params = None, **kwargs):
63
+ if params is None:
64
+ params = ListingPostParams(**kwargs)
65
+ elif not isinstance(params, ListingPostParams):
66
+ raise TypeError("params must be of type ListingPostParams.")
67
+ return requests.post(
68
+ f"{self.__api}/listing",
69
+ params.model_dump(exclude_none=True),
70
+ headers=self.__get_auth_header()
71
+ )
72
+
73
+ def listing_delete(self, uuid):
74
+ UUIDParam(uuid=uuid)
75
+ return requests.delete(f"{self.__api}/listing/{uuid}", headers=self.__get_auth_header())
76
+
77
+ def listing_patch(self, uuid, ops):
78
+ UUIDParam(uuid=uuid)
79
+ ops_objects = map(lambda op: op if isinstance(op, Op) else Op(**op), ops)
80
+ ops_json = list(map(lambda op: op.model_dump(mode="json"), ops_objects))
81
+ headers = {"Content-Type": "application/json-patch+json"}
82
+ headers.update(self.__get_auth_header())
83
+ return requests.patch(f"{self.__api}/listing/{uuid}", json=ops_json, headers=headers)
84
+
85
+ def post_photo(self, listing_uuid, photo, display_order):
86
+ if not (isinstance(photo, Path) or isinstance(photo, str)):
87
+ raise TypeError("Photo must be of type Path or str.")
88
+
89
+ if isinstance(photo, str) and validators.url(photo):
90
+ photo_request = requests.get(photo)
91
+ photo_request.raise_for_status()
92
+ photo_data = photo_request.content
93
+ mime_type = photo_request.headers.get("Content-Type", "application/octet-stream")
94
+ else:
95
+ if isinstance(photo, Path):
96
+ photo_path = photo
97
+ else:
98
+ photo_path = Path(photo)
99
+ if not photo_path.exists() or not photo_path.is_file():
100
+ raise FileNotFoundError(f"File not found: {photo}")
101
+ photo_data = open(photo_path, "rb")
102
+ mime_type = photo_path.suffix[1:]
103
+
104
+ post_photo_request = requests.post(f"{self.__api}/listing/{listing_uuid}/photo", headers=self.__get_auth_header())
105
+ post_photo_request.raise_for_status()
106
+ post_photo_data = post_photo_request.json()['data']
107
+ upload_photo_url = post_photo_data['upload_url']
108
+ photo_id = post_photo_data['id']
109
+
110
+ put_photo_headers = {'Content-Type': f'image/{mime_type}'}
111
+ put_photo_request = requests.put(upload_photo_url, headers=put_photo_headers, data=photo_data)
112
+ put_photo_request.raise_for_status()
113
+
114
+ ops = [Op(op=ListingOps.REPLACE, path=f'/photo/{photo_id}/status', value=ListingPhotoStatus.ACTIVE)]
115
+
116
+ if display_order >= 0:
117
+ ops.append(Op(op=ListingOps.REPLACE, path=f'/photo/{photo_id}/display_order', value=display_order))
118
+ else:
119
+ ops.append(Op(op=ListingOps.REPLACE, path=f'/cover_photo', value=photo_id))
120
+
121
+ listing_patch_request = self.listing_patch(listing_uuid, ops)
122
+ listing_patch_request.raise_for_status()
123
+
124
+ def exchange_search(self, params = None, **kwargs):
125
+ if params is None:
126
+ params = ExchangeParams(**kwargs)
127
+ elif not isinstance(params, ExchangeParams):
128
+ raise TypeError("params must be of type ExchangeParams.")
129
+ return requests.get(f"{self.__api}/exchange", params.model_dump(mode='json'), headers=self.__get_auth_header())
130
+
131
+ def exchange_post(self, params = None, **kwargs):
132
+ if params is None:
133
+ params = ExchangePostParams(**kwargs)
134
+ elif not isinstance(params, ExchangePostParams):
135
+ raise TypeError("params must be of type ExchangePostParams.")
136
+ return requests.post(f"{self.__api}/exchange", json=params.model_dump(mode='json'), headers=self.__get_auth_header())
@@ -0,0 +1,332 @@
1
+ import datetime
2
+ import typing
3
+ from pathlib import Path
4
+ from uuid import UUID
5
+
6
+ import pyotp
7
+ import requests
8
+
9
+ from gameflip_api.enums import ShippingPaidBy, Category, Platform, UPC, ListingStatus, AcceptCurrency, Visibility
10
+ from gameflip_api.params import PriceRange, DatetimeRange, ListingSearchParams, ListingPostParams, Op, ExchangeParams, \
11
+ IntRange, ExchangePostParams
12
+
13
+
14
+ class GameflipAPI:
15
+ __api: str
16
+ __api_key: str
17
+ __secret: str
18
+ __totp: pyotp.totp.TOTP
19
+
20
+ def __init__(self, api_key: str, secret: str) -> None:
21
+ """
22
+ Generates the totp to get authorization for requests.
23
+ :param api_key: API Key is located at developer tab on your profile.
24
+ :param secret: The secret is generated on API Key creation.
25
+ """
26
+ ...
27
+
28
+ def __get_auth_header(self) -> dict: ...
29
+
30
+ def profile(self, uuid: typing.Union[str, UUID] = 'me') -> requests.Response:
31
+ """
32
+ Makes a request to the gameflip API to fetch user profile information, which you can get your own information.
33
+ :param uuid: The id is on the url of any profile, it usually starts with 'us-east' and ends with a bunt of numbers.
34
+ To fetch data from your own profile you can set the id with 'me'.
35
+ :raise pydantic.error_wrappers.ValidationError: If id_ is empty or not a string.
36
+ :return: requests.Response
37
+ """
38
+ ...
39
+
40
+ def wallet_history(self) -> requests.Response:
41
+ """
42
+ Makes a request to the gameflip API to fetch your wallet history.
43
+ :return: requests.Response
44
+ """
45
+ ...
46
+
47
+ @staticmethod
48
+ def get_rldata_items() -> requests.Response:
49
+ """
50
+ Makes a request to the gameflip API to fetch Rocket League items data.
51
+ :return: requests.Response
52
+ """
53
+ ...
54
+
55
+ @classmethod
56
+ @typing.overload
57
+ def listing_search(cls, params: ListingSearchParams) -> requests.Response: ...
58
+
59
+ @classmethod
60
+ @typing.overload
61
+ def listing_search(
62
+ cls,
63
+ *,
64
+ term: typing.Optional[str] = None,
65
+ category: typing.Optional[Category] = None,
66
+ platform: typing.Optional[Platform] = None,
67
+ genre: typing.Optional[str] = None,
68
+ upc: typing.Optional[UPC] = None,
69
+ shipping_paid_by: typing.Optional[ShippingPaidBy] = None,
70
+ digital: typing.Optional[bool] = None,
71
+ status: typing.Optional[ListingStatus] = None,
72
+ owner: typing.Optional[str] = None,
73
+ condition: typing.Optional[str] = None,
74
+ condition_min: typing.Optional[str] = None,
75
+ price: typing.Optional[PriceRange] = None,
76
+ created: typing.Optional[DatetimeRange] = None,
77
+ updated: typing.Optional[DatetimeRange] = None,
78
+ expiration: typing.Optional[DatetimeRange] = None,
79
+ seller_online_until: typing.Optional[datetime.datetime] = None,
80
+ tags: typing.Optional[str] = None,
81
+ start: typing.Optional[int] = None,
82
+ limit: typing.Optional[int] = None
83
+ ) -> requests.Response:
84
+ """
85
+ Makes a get to /api/v1/listing
86
+ ::param term: Searches listing name and description and generates relevance score
87
+ ::param category: Filters category
88
+ ::param platform: Filters platform
89
+ ::param genre: Filters genre
90
+ ::param upc: Filters UPC (Universal Product Code), games in this case
91
+ ::param shipping_paid_by: Filters shipping paid by
92
+ ::param digital: Filters if it's a digital listing
93
+ ::param status: Filters status
94
+ ::param owner: Filters owner
95
+ ::param condition: Filters condition
96
+ ::param condition_min: Filters condition (any one value defined for condition)
97
+ ::param price: Filters price
98
+ ::param created: Filters in a range when it has been created
99
+ ::param updated: Filters in a range when it has been updated
100
+ ::param expiration: Filters in a range for its expiration
101
+ ::param seller_online_until: Filters sellers online
102
+ ::param tags: A special term filter which also allows logical AND using the carrot ^ operator in addition
103
+ to logical OR with comma ,
104
+ Precedence is given to OR operations over AND so that a filter like tags=a,b^c^d,e will match all
105
+ listings that have (a OR b) AND (c) AND (d OR e).
106
+ ::param start: Gets listing by page number
107
+ ::param limit: Limit the number of listings returned
108
+ ::raise pydantic.error_wrappers.ValidationError: If some parameter is not correct type
109
+ ::raise TypeError: params is not ListingPostParams neither None
110
+ ::return requests.Response: Returns a response object
111
+ """
112
+ ...
113
+
114
+ @typing.overload
115
+ def my_listing_search(self, params: ListingSearchParams) -> requests.Response: ...
116
+
117
+ @typing.overload
118
+ def my_listing_search(
119
+ self,
120
+ *,
121
+ term: typing.Optional[str] = None,
122
+ category: typing.Optional[Category] = None,
123
+ platform: typing.Optional[Platform] = None,
124
+ genre: typing.Optional[str] = None,
125
+ upc: typing.Optional[UPC] = None,
126
+ shipping_paid_by: typing.Optional[ShippingPaidBy] = None,
127
+ digital: typing.Optional[bool] = None,
128
+ status: typing.Optional[ListingStatus] = None,
129
+ owner: typing.Optional[str] = None,
130
+ condition: typing.Optional[str] = None,
131
+ condition_min: typing.Optional[str] = None,
132
+ price: typing.Optional[PriceRange] = None,
133
+ created: typing.Optional[DatetimeRange] = None,
134
+ updated: typing.Optional[DatetimeRange] = None,
135
+ expiration: typing.Optional[DatetimeRange] = None,
136
+ seller_online_until: typing.Optional[datetime.datetime] = None,
137
+ tags: typing.Optional[str] = None,
138
+ start: typing.Optional[int] = None,
139
+ limit: typing.Optional[int] = None
140
+ ) -> requests.Response:
141
+ """
142
+ Makes a get to /api/v1/listing
143
+ ::param term: Searches listing name and description and generates relevance score
144
+ ::param category: Filters category
145
+ ::param platform: Filters platform
146
+ ::param genre: Filters genre
147
+ ::param upc: Filters UPC (Universal Product Code), games in this case
148
+ ::param shipping_paid_by: Filters shipping paid by
149
+ ::param digital: Filters if it's a digital listing
150
+ ::param status: Filters status
151
+ ::param owner: Filters owner
152
+ ::param condition: Filters condition
153
+ ::param condition_min: Filters condition (any one value defined for condition)
154
+ ::param price: Filters price
155
+ ::param created: Filters in a range when it has been created
156
+ ::param updated: Filters in a range when it has been updated
157
+ ::param expiration: Filters in a range for its expiration
158
+ ::param seller_online_until: Filters sellers online
159
+ ::param tags: A special term filter which also allows logical AND using the carrot ^ operator in addition
160
+ to logical OR with comma ,
161
+ Precedence is given to OR operations over AND so that a filter like tags=a,b^c^d,e will match all
162
+ listings that have (a OR b) AND (c) AND (d OR e).
163
+ ::param start: Gets listing by page number
164
+ ::param limit: Limit the number of listings returned
165
+ ::raise pydantic.error_wrappers.ValidationError: If some parameter is not correct type
166
+ ::raise TypeError: params is not ListingPostParams neither None
167
+ ::return requests.Response: Returns a response object
168
+ """
169
+ ...
170
+
171
+ @classmethod
172
+ def listing_of(cls, uuid: typing.Union[str, UUID]) -> requests.Response:
173
+ """
174
+ Makes a request to the gameflip API to fetch listing information.
175
+ :param uuid: The id is on the url of any listing, it usually made with a bunt of numbers.
176
+ :raise pydantic.error_wrappers.ValidationError: If id_ is empty or not a string.
177
+ :return: requests.Response
178
+ """
179
+ ...
180
+
181
+ def my_listing_of(self, uuid: typing.Union[str, UUID]) -> requests.Response:
182
+ """
183
+ Makes a request to the gameflip API to fetch listing information. Can fetch your own private listing.
184
+ :param uuid: The id is on the url of any listing, it usually made with a bunt of numbers.
185
+ :raise pydantic.error_wrappers.ValidationError: If id_ is empty or not a string.
186
+ :return: requests.Response
187
+ """
188
+ ...
189
+
190
+ @typing.overload
191
+ def listing_post(self, params: ListingPostParams) -> requests.Response: ...
192
+
193
+ @typing.overload
194
+ def listing_post(
195
+ self,
196
+ *,
197
+ name: typing.Optional[str] = None,
198
+ description: typing.Optional[str] = None,
199
+ price: typing.Optional[int] = None,
200
+ category: typing.Optional[Category] = None,
201
+ upc: typing.Optional[UPC] = None,
202
+ accept_currency: AcceptCurrency = AcceptCurrency.USD,
203
+ digital: bool = False,
204
+ visibility: Visibility = Visibility.PUBLIC
205
+ ) -> requests.Response:
206
+ """
207
+ Makes a post to /api/v1/listing to create a new listing
208
+ :param name: Name of listing
209
+ :param description: Description of listing
210
+ :param price: Price of listing
211
+ :param category: Category of listing
212
+ :param upc: UPC of listing
213
+ :param accept_currency: Accept currency of the listing
214
+ :param digital: Specifies if it's a digital listing
215
+ :param visibility: Visibility of listing
216
+ :raise pydantic.error_wrappers.ValidationError: If some parameter is not correct type or price is invalid
217
+ :return: requests.Response
218
+ """
219
+ ...
220
+
221
+ def listing_delete(self, uuid: typing.Union[str, UUID]) -> requests.Response:
222
+ """
223
+ Makes a request to the gameflip API to delete a listing
224
+ :param uuid: The id of the listing
225
+ :return: requests.Response
226
+ """
227
+ ...
228
+
229
+ def listing_patch(self, uuid: typing.Union[str, UUID], ops: typing.List[typing.Union[Op, typing.Dict]]) -> requests.Response:
230
+ """
231
+ Makes a request to the gameflip API to update a listing
232
+ :param uuid: The id of the listing
233
+ :param ops: Operations to apply
234
+ :return: requests.Response
235
+ """
236
+ ...
237
+
238
+ def post_photo(self, listing_uuid: typing.Union[str, UUID], photo: typing.Union[str, Path], display_order: int):
239
+ """
240
+ Makes a serie of requests to update a listing photo
241
+ :param listing_uuid: The id of the listing
242
+ :param photo: Url or Path for the photo
243
+ :param display_order: Display order of the photo
244
+ :raise TypeError: photo is not a string or a Path
245
+ :raise HTTPError: some request goes wrong
246
+ :raise FileNotFoundError: photo path not found or don't exist
247
+ :return: None
248
+ """
249
+ ...
250
+
251
+ @typing.overload
252
+ def exchange_search(self, params: ExchangeParams) -> requests.Response: ...
253
+
254
+ @typing.overload
255
+ def exchange_search(
256
+ self,
257
+ *,
258
+ role: typing.Optional[ShippingPaidBy] = None,
259
+ id: typing.Optional[str, UUID] = None,
260
+ name: typing.Optional[str] = None,
261
+ buyer: typing.Optional[str] = None,
262
+ seller: typing.Optional[str] = None,
263
+ price: typing.Optional[PriceRange] = None,
264
+ risk: typing.Optional[IntRange] = None,
265
+ status: typing.Optional[ShippingPaidBy] = None,
266
+ start: typing.Optional[int] = None,
267
+ buyer_rated: typing.Optional[bool] = None,
268
+ seller_rated: typing.Optional[bool] = None,
269
+ category: typing.Optional[Category] = None,
270
+ upc: typing.Optional[UPC] = None,
271
+ digital: typing.Optional[bool] = None,
272
+ escrow: typing.Optional[bool] = None,
273
+ settled: typing.Optional[DatetimeRange] = None,
274
+ shipped: typing.Optional[DatetimeRange] = None,
275
+ received: typing.Optional[DatetimeRange] = None,
276
+ auto_rate_after: typing.Optional[DatetimeRange] = None,
277
+ created: typing.Optional[DatetimeRange] = None,
278
+ updated: typing.Optional[DatetimeRange] = None,
279
+ version: typing.Optional[int] = None,
280
+ limit: typing.Optional[int] = None,
281
+ ) -> requests.Response:
282
+ """
283
+ Makes a request to the gameflip API to fetch exchange
284
+ ::param role: buyer or seller
285
+ ::param id: uuid of the exchange
286
+ ::param name: Name of listing
287
+ ::param buyer: uuid of the buyer
288
+ ::param seller: uuid of the seller
289
+ ::param price: price range of the exchange
290
+ ::param risk: risk range of the exchange
291
+ ::param status: status of the exchange
292
+ ::param start: start page of the fetch
293
+ ::param buyer_rated: buyer rated (True/False)
294
+ ::param seller_rated: seller rated (True/False)
295
+ ::param category: category of the listing
296
+ ::param upc: UPC of the listing
297
+ ::param digital: specifies if it's a digital listing
298
+ ::param escrow: specifies if it's exchange with escrow
299
+ ::param settled: settled time range of the listing
300
+ ::param shipped: shipped time range of the listing
301
+ ::param received: received time range of the listing
302
+ ::param auto_rate_after: time range
303
+ ::param created: time range of the listing
304
+ ::param updated: time range of the listing
305
+ ::param version: version of the exchange
306
+ ::param limit: limit the fetch
307
+ ::raise pydantic.error_wrappers.ValidationError: If some parameter is not correct type
308
+ ::raise TypeError: params is not ExchangeParams neither None
309
+ :return: requests.Response
310
+ """
311
+ ...
312
+
313
+ @typing.overload
314
+ def exchange_post(self, params: ExchangePostParams) -> requests.Response: ...
315
+
316
+ @typing.overload
317
+ def exchange_post(
318
+ self,
319
+ *,
320
+ listing_id: typing.Union[str, UUID],
321
+ source_id: typing.Union[str, UUID],
322
+ address_id: typing.Union[str, UUID],
323
+ ):
324
+ """
325
+ Makes a request to the gameflip API to buy a listing
326
+ :param listing_id: listing id
327
+ :param source_id: source id
328
+ :param address_id: address id
329
+ ::raise pydantic.error_wrappers.ValidationError: If some parameter is not correct type
330
+ ::raise TypeError: params is not ExchangePostParams neither None
331
+ :return: requests.Response
332
+ """
@@ -0,0 +1,214 @@
1
+ from enum import Enum
2
+
3
+ class ShippingPaidBy(str, Enum):
4
+ BUYER = "buyer"
5
+ SELLER = "seller"
6
+
7
+
8
+ class Category(str, Enum):
9
+ GAMES = 'CONSOLE_VIDEO_GAMES' # Video games, digital or physical
10
+ INGAME = 'DIGITAL_INGAME' # In-game items, digital only
11
+ GIFTCARD = 'GIFTCARD' # Gift cards, digital or physical
12
+ CONSOLE = 'VIDEO_GAME_HARDWARE' # Console game hardware, physical listing only
13
+ ACCESSORIES = 'VIDEO_GAME_ACCESSORIES' # Console game accessories, physical listing only
14
+ TOYS = 'TOYS_AND_GAMES' # Collectibles, physical listing only
15
+ VIDEO = 'VIDEO_DVD' # Movies, physical or digital
16
+ OTHER = 'UNKNOWN' # Unsupported category
17
+
18
+
19
+ class Kind(str, Enum):
20
+ ITEM = 'item' # Item selling
21
+ GIG = 'gig' # Gig selling
22
+
23
+
24
+ class UPC(str, Enum):
25
+ ANIMAL_CROSSING_NH = "045496596439"
26
+ BORDERLANDS3_PC = "GFPCBDLANDS3"
27
+ BORDERLANDS3_PS4 = "GFPSBDLANDS3"
28
+ BORDERLANDS3_XONE = "GFXOBDLANDS3"
29
+ BRAWLHALLA = "045496663285"
30
+ BRAWL_STARS = "GFBRAWLSTARS"
31
+ CLASH_OF_CLANS = "GFCLASHOCLAN"
32
+ CLASH_ROYALE = "GFCLASHROYAL"
33
+ COD_BLOPS6 = "7340f6b6-3a5a-4737-9033-237b9fb522ca"
34
+ COD_MWII = "GF0000CODMW2"
35
+ COD_MWIII = "GF0000CODMW3"
36
+ COD_WZ = "GF00000CODWZ"
37
+ CS2 = "094922417596"
38
+ DARK_AND_DARKER = "GF00DADARKER"
39
+ DARK_SOULS_3 = "GFDARKSOULS3"
40
+ DCU_ONLINE = "GF000000DCUO"
41
+ DEAD_BY_DAYLIGHT = "GFDBDAYLIGHT"
42
+ DIABLO_4 = "GF000DIABLO4"
43
+ DOTA2 = "111111111111"
44
+ DRAGONS_DOGMA_2 = "GFDRAGONSDM2"
45
+ DREAMLIGHT_VALLEY = "GFDREAMLIGHT"
46
+ DYING_LIGHT_2_PS4 = "662248923314"
47
+ DYING_LIGHT_2_PS5 = "662248924915"
48
+ DYING_LIGHT_2_XONE = "662248923369"
49
+ DYING_LIGHT_2_XSERIES = "662248924823"
50
+ ELDEN_RING_PC = "GF000ERINGPC"
51
+ ELDEN_RING_PS5 = "GF000ERINGPS"
52
+ ELDEN_RING_XSERIES = "GF000ERINGXS"
53
+ ENSHROUDED = "GFENSHROUDED"
54
+ ESO_PC = "GF00000PCESO"
55
+ ESO_PS5 = "GF00000PSESO"
56
+ ESO_XSERIES = "GF00000XSESO"
57
+ EVE_ONLINE = "GF0000000EVE"
58
+ FALLOUT76_PC = "GFPCFLLOUT76"
59
+ FALLOUT76_PS4 = "GFPSFLLOUT76"
60
+ FALLOUT76_XONE = "GFXOFLLOUT76"
61
+ FIFA = "GF000000FIFA"
62
+ FORTNITE = "GFFORTNITE"
63
+ GROWTOPIA = "GF0GROWTOPIA"
64
+ GTA5_PC = "710425414534"
65
+ GTA5_PS4 = "710425474521"
66
+ GTA5_PS5 = "GF000GTA5PS5"
67
+ GTA5_XONE = "710425494512"
68
+ GTA5_XSERIES = "GF000GTA5XSX"
69
+ HALO_INFINITE = "GF000HALOINF"
70
+ HAY_DAY = "GF0000HAYDAY"
71
+ LEAGUE_OF_LEGENDS = "GF0000000LOL"
72
+ LORDS_OF_THE_FALLEN = "GFLORDFALLEN"
73
+ MADDEN = "GF0000MADDEN"
74
+ MONOPOLY_GO = "GFMONOPOLYGO"
75
+ NO_MANS_SKY = "GF0NOMANSSKY"
76
+ PATH_OF_EXILE = "GF000POEXILE"
77
+ PATH_OF_EXILE_2 = "6ce9c6d3-c32d-4037-9554-3f1f9de866bd"
78
+ POKEMON_DIAMOND_PEARL = "045496597894"
79
+ POKEMON_LEGENDS_ARCEUS = "045496598044"
80
+ POKEMON_SCARLET_VIOLET = "045496599058"
81
+ POKEMON_SWORD_SHIELD = "045496596972"
82
+ PUBG = "000000578080"
83
+ PUBG_MOBILE = "GF00PUBGMOBI"
84
+ RUNESCAPE = "GF0RUNESCAPE"
85
+ RUST = "000000252490"
86
+ RUST_CONSOLE = "GFRUSTCONSOL"
87
+ SEA_OF_THIEVES = "GFSEATHIEVES"
88
+ SKULL_BONES = "GFSKULLBONES"
89
+ STATE_OF_DECAY_2 = "GF00SODECAY2"
90
+ SWTOR = "GF00000SWTOR"
91
+ TF2 = "014633098693"
92
+ TINY_TINAS_WONDERLANDS = "GF00TTWONDER"
93
+ WARFRAME_PC = "GFSTWARFRAME"
94
+ WARFRAME_PS = "GFPSWARFRAME"
95
+ WARFRAME_SWITCH = "GFSWWARFRAME"
96
+ WARFRAME_XBOX = "GFXOWARFRAME"
97
+ WORLD_OF_WARCRAFT = "GF000WOW0000"
98
+ XDEFIANT = "GF00XDEFIANT"
99
+
100
+
101
+ class Platform(str, Enum):
102
+ XBOX = 'xbox'
103
+ X360 = 'xbox_360'
104
+ XONE = 'xbox_one'
105
+ PS1 = 'playstation'
106
+ PS2 = 'playstation_2'
107
+ PS3 = 'playstation_3'
108
+ PS4 = 'playstation_4'
109
+ PSP = 'playstation_portable'
110
+ PSVITA = 'playstation_vita'
111
+ N64 = 'nintendo_64'
112
+ NGAMECUBE = 'nintendo_gamecube'
113
+ NWII = 'nintendo_wii'
114
+ NWIIU = 'nintendo_wiiu'
115
+ NSWITCH = 'nintendo_switch'
116
+ NDS = 'nintendo_ds'
117
+ NDSI = 'nintendo_dsi'
118
+ N3DS = 'nintendo_3ds'
119
+ STEAM = 'steam'
120
+ ORIGIN = 'origin'
121
+ UPLAY = 'uplay'
122
+ GOG = 'gog'
123
+ MOBILE = 'mobile'
124
+ BATTLENET = 'battlenet'
125
+ XLIVE = 'xbox_live'
126
+ PSN = 'playstation_network'
127
+ UNKNOWN = 'unknown' # For PC platform, use UNKNOWN
128
+
129
+
130
+ class ShippingWithinDays(int, Enum):
131
+ AUTO = 0
132
+ ONE = 1
133
+ TWO = 2
134
+ THREE = 3
135
+
136
+
137
+ class ExpireInDays(int, Enum):
138
+ SEVEN = 7
139
+ FOURTEEN = 14
140
+ THIRTY = 30
141
+
142
+
143
+ class EscrowStatus(str, Enum):
144
+ START = 'start' # Initial condition: seller has Steam item(s)
145
+ RECEIVE_PENDING = 'receive_pending' # Offer made to seller to get Steam item(s)
146
+ RECEIVED = 'received' # Gameflip has Steam item(s)
147
+ LISTED = 'listed' # Gameflip has created listings for Steam item(s)
148
+ STEAM_ESCROW = 'steam_escrow' # Steam item(s) is held by Steam in escrow
149
+ TRADE_HOLD = 'trade_hold' # Gameflip has item(s) but there is a trade hold on them (e.g., Just Survive)
150
+ DELIVER_PENDING = "deliver_pending" # Escrow status: Offer made to buyer, but not accepted yet
151
+ DELIVERED = "delivered" # Escrow status: Buyer has item (terminal state)
152
+ RETURN_PENDING = "return_pending" # Escrow status: Trade offer made to seller to return item, but not accepted yet
153
+ RETURNED = "returned" # Escrow status: Seller has accepted return of item
154
+
155
+
156
+ class ListingStatus(str, Enum):
157
+ DRAFT = "draft" # Listing is draft/editing mode. You cannot list it when it's in this mode
158
+ READY = "ready" # Listing is ready to be listed, required fields have been filled
159
+ ONSALE = "onsale" # Listing is published to the public
160
+ SALE_PENDING = "sale_pending" # A buyer just bought the listing, and payment is being processed
161
+ SOLD = "sold" # A buyer had bought the listing
162
+
163
+
164
+ class ListingPhotoStatus(str, Enum):
165
+ PENDING = "pending"
166
+ ACTIVE = "active"
167
+ DELETED = "deleted"
168
+
169
+
170
+ class ListingOps(str, Enum):
171
+ TEST = "test"
172
+ ADD = "add"
173
+ REPLACE = "replace"
174
+ REMOVE = "remove"
175
+
176
+
177
+ class ExchangeStatus(str, Enum):
178
+ PENDING = "pending" # Exchange is being created
179
+ PENDING_CANCEL = "pending_cancel" # Exchange is being canceled
180
+ PENDING_RESCINDING = "pending_rescinding" # Exchange is being refunded
181
+ SETTLED = "settled" # Exchange has payment settled (verified and approved)
182
+ RECEIVED = "received" # Buyer has received the item
183
+ PENDING_COMPLETION = "pending_completion" # Exchange is being completed, happens after seller rates
184
+ COMPLETE = "complete" # Exchange completes, both buyer and seller have rated each other
185
+ CANCELLED = "cancelled" # Exchange has been cancelled, and payment authorization (if any) is also cancelled
186
+ RESCINDED = "rescinded" # Exchange has been cancelled with refund completed
187
+
188
+
189
+ class AcceptCurrency(str, Enum):
190
+ USD = "USD"
191
+ FLP = "FLP"
192
+ BOTH = "BOTH"
193
+
194
+
195
+ # Steam APP IDs and Context IDs can be defined in a similar manner if needed.
196
+ class SteamAppID(str, Enum):
197
+ CSGO = '730' # CS:GO
198
+ TF2 = '440' # Team Fortress 2
199
+ DOTA2 = '570' # DOTA 2
200
+ RUST = '252490' # Rust
201
+ PUBG = '578080' # PlayerUnknown's Battlegrounds
202
+ H1Z1_KOK = '433850' # H1Z1: King of the Kill
203
+ JUST_SURVIVE = '295110' # H1Z1: Just Survive
204
+
205
+
206
+ class SteamContextID(str, Enum):
207
+ CONTEXT_ID_433850 = '1' # H1Z1: King of the Kill
208
+ CONTEXT_ID_295110 = '1' # Just Survive
209
+ CONTEXT_ID_DEFAULT = '2' # Default if not specified above
210
+
211
+
212
+ class Visibility(str, Enum):
213
+ UNLISTED = 'unlisted'
214
+ PUBLIC = 'public'
@@ -0,0 +1,134 @@
1
+ import datetime
2
+ import typing
3
+ from uuid import UUID
4
+
5
+ from pydantic import BaseModel, Field, field_validator
6
+ from pydantic_core.core_schema import ValidationInfo
7
+
8
+ from gameflip_api.enums import Category, Platform, UPC, ShippingPaidBy, ListingStatus, AcceptCurrency, Visibility, \
9
+ ListingOps
10
+ from gameflip_api.types import AwsCognitoUUID, Price
11
+
12
+ T = typing.TypeVar("T")
13
+
14
+ class Range(BaseModel, typing.Generic[T]):
15
+ start: T
16
+ end: T
17
+
18
+ @field_validator("end", mode="after")
19
+ @classmethod
20
+ def check_end_after_start(cls, value: int, info: ValidationInfo) -> int:
21
+ if "start" in info.data and value < info.data["start"]:
22
+ raise ValueError("start must be greater or equal to end.")
23
+ return value
24
+
25
+ def __str__(self):
26
+ return f"{self.start},{self.end}"
27
+
28
+
29
+ class IntRange(Range[int]):
30
+ pass
31
+
32
+
33
+ class PriceRange(Range[Price]):
34
+ pass
35
+
36
+
37
+ class DatetimeRange(Range[datetime.datetime]):
38
+ def __str__(self):
39
+ return f"{self.__format_datetime(self.start)},{self.__format_datetime(self.end)}"
40
+
41
+ @staticmethod
42
+ def __format_datetime(datetime_: datetime.datetime):
43
+ return datetime_.isoformat(timespec='milliseconds') + "Z"
44
+
45
+
46
+ class ListingSearchParams(BaseModel):
47
+ term: typing.Optional[str] = None
48
+ category: typing.Optional[Category] = None
49
+ platform: typing.Optional[Platform] = None
50
+ genre: typing.Optional[str] = None
51
+ upc: typing.Optional[UPC] = None
52
+ shipping_paid_by: typing.Optional[ShippingPaidBy] = None
53
+ digital: typing.Optional[bool] = None
54
+ status: typing.Optional[ListingStatus] = None
55
+ owner: typing.Optional[str] = None
56
+ condition: typing.Optional[str] = None
57
+ condition_min: typing.Optional[str] = None
58
+ price: typing.Optional[PriceRange] = None
59
+ created: typing.Optional[DatetimeRange] = None
60
+ updated: typing.Optional[DatetimeRange] = None
61
+ expiration: typing.Optional[DatetimeRange] = None
62
+ seller_online_until: typing.Optional[DatetimeRange] = None
63
+ tags: typing.Optional[str] = None
64
+ start: typing.Optional[int] = None
65
+ limit: typing.Optional[int] = None
66
+
67
+ def model_dump(self, *args, **kwargs):
68
+ d = super().model_dump(*args, **kwargs)
69
+ for attr in ["price", "created", "updated", "expiration", "seller_online_until"]:
70
+ if d.get(attr) is not None:
71
+ d[attr] = str(getattr(self, attr))
72
+ return d
73
+
74
+
75
+ class ListingPostParams(BaseModel):
76
+ name: typing.Optional[str] = None
77
+ description: typing.Optional[str] = None
78
+ price: typing.Optional[Price] = None
79
+ category: typing.Optional[Category] = None
80
+ upc: typing.Optional[UPC] = None
81
+ accept_currency: AcceptCurrency = AcceptCurrency.USD
82
+ digital: bool = False
83
+ visibility: Visibility = Visibility.PUBLIC
84
+
85
+
86
+ class GameflipAPIParams(BaseModel):
87
+ api_key: str = Field(..., min_length=1, description="API key is required.")
88
+ secret: str = Field(..., min_length=1, description="Secret is required.")
89
+
90
+
91
+ class UUIDParam(BaseModel):
92
+ uuid: UUID
93
+
94
+
95
+ class OwnerParam(BaseModel):
96
+ owner: AwsCognitoUUID
97
+
98
+
99
+ class Op(BaseModel):
100
+ op: ListingOps
101
+ path: str
102
+ value: typing.Any
103
+
104
+
105
+ class ExchangeParams(BaseModel):
106
+ role: typing.Optional[ShippingPaidBy] = None
107
+ id: typing.Optional[UUID] = None
108
+ name: typing.Optional[str] = None
109
+ buyer: typing.Optional[str] = None
110
+ seller: typing.Optional[str] = None
111
+ price: typing.Optional[PriceRange] = None
112
+ risk: typing.Optional[IntRange] = None
113
+ status: typing.Optional[ListingStatus] = None
114
+ buyer_rated : typing.Optional[bool] = None
115
+ seller_rated : typing.Optional[bool] = None
116
+ start: typing.Optional[int] = None
117
+ category: typing.Optional[Category] = None
118
+ upc: typing.Optional[UPC] = None
119
+ digital: typing.Optional[bool] = None
120
+ escrow: typing.Optional[bool] = None
121
+ settled: typing.Optional[DatetimeRange] = None
122
+ shipped: typing.Optional[DatetimeRange] = None
123
+ received: typing.Optional[DatetimeRange] = None
124
+ auto_rate_after: typing.Optional[DatetimeRange] = None
125
+ created: typing.Optional[DatetimeRange] = None
126
+ updated: typing.Optional[DatetimeRange] = None
127
+ version: typing.Optional[int] = None
128
+ limit: typing.Optional[int] = None
129
+
130
+
131
+ class ExchangePostParams(BaseModel):
132
+ listing_id: UUID
133
+ source_id: UUID
134
+ address_id: UUID
@@ -0,0 +1,60 @@
1
+ import re
2
+ from uuid import UUID
3
+
4
+ from pydantic import GetCoreSchemaHandler
5
+ from pydantic_core import core_schema
6
+
7
+
8
+ class AwsCognitoUUID(str):
9
+ """
10
+ Tipo customizado que valida strings no formato:
11
+ <region>:<uuid>
12
+ Exemplo: us-east-1:dc501f75-302c-4419-8ece-57974d688e6f
13
+ """
14
+
15
+ pattern = re.compile(
16
+ r"^[a-z]{2}-[a-z]+-\d+:[0-9a-fA-F-]{36}$"
17
+ )
18
+
19
+ @classmethod
20
+ def __get_pydantic_core_schema__(cls, _source_type, _handler: GetCoreSchemaHandler):
21
+ return core_schema.no_info_after_validator_function(
22
+ cls.validate,
23
+ core_schema.str_schema()
24
+ )
25
+
26
+ @classmethod
27
+ def validate(cls, value):
28
+ if not isinstance(value, str):
29
+ raise TypeError("valor should be string.")
30
+
31
+ if not cls.pattern.match(value):
32
+ raise ValueError("invalid format (expected '<region>:<uuid>')")
33
+
34
+ # Valida se a parte após ':' é um UUID válido
35
+ region, uuid_part = value.split(":", 1)
36
+ try:
37
+ UUID(uuid_part)
38
+ except ValueError:
39
+ raise ValueError("UUID invalid after ':'")
40
+
41
+ return cls(value)
42
+
43
+
44
+ class Price(int):
45
+ @classmethod
46
+ def __get_pydantic_core_schema__(cls, _source_type, _handler: GetCoreSchemaHandler):
47
+ return core_schema.no_info_after_validator_function(
48
+ cls.validate,
49
+ core_schema.int_schema()
50
+ )
51
+
52
+ @classmethod
53
+ def validate(cls, value):
54
+ if not isinstance(value, int):
55
+ raise TypeError("value should be int.")
56
+
57
+ if value < 75:
58
+ raise ValueError("value should be higher than 75.")
59
+
60
+ return cls(value)
@@ -0,0 +1,82 @@
1
+ Metadata-Version: 2.1
2
+ Name: gameflip-api
3
+ Version: 1.0.1
4
+ Summary: Uma API wrapper em Python para utilização com a plataforma Gameflip.
5
+ Author-email: Gustavo Pedroso Bernardes <gpedrosobernardes@gmail.com>
6
+ Requires-Python: >=3.8
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: validators
9
+ Requires-Dist: pydantic
10
+ Requires-Dist: pyotp
11
+ Requires-Dist: requests
12
+
13
+ # Gameflip API
14
+
15
+ Uma **API wrapper em Python** para utilização com a plataforma Gameflip.
16
+ Facilita a integração via script, para automatizar operações, consultar dados e interagir com a Gameflip de forma programática.
17
+
18
+ ## 🧰 Funcionalidades
19
+
20
+ * Conexão autenticada com a Gameflip via chave e segredo (`API_KEY`, `API_SECRET`)
21
+ * Métodos para executar operações comuns (por exemplo: listagem de produtos, criação de ofertas, consulta de histórico)
22
+ * Interface simples em Python para agilizar automações
23
+ * Código open-source, fácil de estender para necessidades específicas
24
+
25
+ ## 🚀 Começando
26
+
27
+ ### Pré-requisitos
28
+
29
+ * Python (versão compatível — idealmente 3.8+)
30
+ * Conta na Gameflip e acesso à API (chave + segredo)
31
+ * Variáveis de ambiente configuradas:
32
+
33
+ ```bash
34
+ export GFAPI_KEY=<sua_chave_aqui>
35
+ export GFAPI_SECRET=<seu_segredo_aqui>
36
+ ```
37
+
38
+ (No Windows: `set GFAPI_KEY=<…>`, `set GFAPI_SECRET=<…>`)
39
+
40
+ ### Instalação
41
+
42
+ ```bash
43
+ pip install gameflip-api
44
+ ```
45
+
46
+ ### Uso básico
47
+
48
+ Exemplo no arquivo `example.py` (ajuste conforme a sua necessidade):
49
+
50
+ ```python
51
+ import os
52
+ from pprint import pprint
53
+ from dotenv import load_dotenv
54
+ from gameflip_api.api import GameflipAPI
55
+
56
+ load_dotenv()
57
+
58
+ print("Listing results:")
59
+
60
+ listing_response = GameflipAPI.listing_search(digital=True, limit=1)
61
+ pprint(listing_response.json())
62
+
63
+ gameflip_api = GameflipAPI(os.getenv('GFAPI_KEY'), os.getenv('GFAPI_SECRET'))
64
+
65
+ print("My profile info:")
66
+
67
+ pprint(gameflip_api.profile().json())
68
+ ```
69
+
70
+ ## 📦 Estrutura do Projeto
71
+
72
+ * `src/gameflip_api/` — código-fonte da biblioteca
73
+ * `example.py` — script de demonstração de uso
74
+ * `test.py` — arquivo para testes rápidos
75
+ * `requirements.txt` — dependências do Python
76
+
77
+ ## 🤝 Contato
78
+
79
+ Se tiver dúvidas, sugestões ou quiser colaborar:
80
+
81
+ * Crie uma *issue* no próprio repositório
82
+ * Envie um pull request com descrições claras das alterações
@@ -0,0 +1,13 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/gameflip_api/__init__.py
4
+ src/gameflip_api/api.py
5
+ src/gameflip_api/api.pyi
6
+ src/gameflip_api/enums.py
7
+ src/gameflip_api/params.py
8
+ src/gameflip_api/types.py
9
+ src/gameflip_api.egg-info/PKG-INFO
10
+ src/gameflip_api.egg-info/SOURCES.txt
11
+ src/gameflip_api.egg-info/dependency_links.txt
12
+ src/gameflip_api.egg-info/requires.txt
13
+ src/gameflip_api.egg-info/top_level.txt
@@ -0,0 +1,4 @@
1
+ validators
2
+ pydantic
3
+ pyotp
4
+ requests
@@ -0,0 +1 @@
1
+ gameflip_api