triangle-api 0.1.5__tar.gz → 0.1.6__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.
- {triangle_api-0.1.5 → triangle_api-0.1.6}/PKG-INFO +1 -1
- {triangle_api-0.1.5 → triangle_api-0.1.6}/setup.py +1 -1
- triangle_api-0.1.6/triangle_api/__init__.py +4 -0
- {triangle_api-0.1.5 → triangle_api-0.1.6}/triangle_api/client.py +16 -17
- {triangle_api-0.1.5 → triangle_api-0.1.6}/triangle_api.egg-info/PKG-INFO +1 -1
- triangle_api-0.1.5/triangle_api/__init__.py +0 -4
- {triangle_api-0.1.5 → triangle_api-0.1.6}/README.md +0 -0
- {triangle_api-0.1.5 → triangle_api-0.1.6}/setup.cfg +0 -0
- {triangle_api-0.1.5 → triangle_api-0.1.6}/triangle_api/exceptions.py +0 -0
- {triangle_api-0.1.5 → triangle_api-0.1.6}/triangle_api.egg-info/SOURCES.txt +0 -0
- {triangle_api-0.1.5 → triangle_api-0.1.6}/triangle_api.egg-info/dependency_links.txt +0 -0
- {triangle_api-0.1.5 → triangle_api-0.1.6}/triangle_api.egg-info/requires.txt +0 -0
- {triangle_api-0.1.5 → triangle_api-0.1.6}/triangle_api.egg-info/top_level.txt +0 -0
|
@@ -5,7 +5,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
|
|
|
5
5
|
|
|
6
6
|
setup(
|
|
7
7
|
name="triangle-api", # Nome que aparecerá no PyPI
|
|
8
|
-
version="0.1.
|
|
8
|
+
version="0.1.6",
|
|
9
9
|
author="Diogo Bastos",
|
|
10
10
|
author_email="seu-email@exemplo.com", # Opcional
|
|
11
11
|
description="Unofficial Python wrapper for Canadian Tire Triangle Mastercard API",
|
|
@@ -13,12 +13,10 @@ class TriangleClient:
|
|
|
13
13
|
self.account_data = None
|
|
14
14
|
|
|
15
15
|
def _get_csrf(self, context):
|
|
16
|
-
"""Auxiliar para extrair o token CSRF dos cookies."""
|
|
17
16
|
cookies = context.cookies()
|
|
18
17
|
return next((c['value'] for c in cookies if c['name'] == 'csrftoken'), "")
|
|
19
18
|
|
|
20
19
|
def _fetch_account_data(self, page, csrf_token):
|
|
21
|
-
"""Faz a chamada manual do retrieveAccount com CSRF."""
|
|
22
20
|
js_get_account = """
|
|
23
21
|
async (csrf) => {
|
|
24
22
|
const r = await fetch('/dash/v1/account/retrieveAccount', {
|
|
@@ -33,13 +31,17 @@ class TriangleClient:
|
|
|
33
31
|
return r.ok ? await r.json() : null;
|
|
34
32
|
}
|
|
35
33
|
"""
|
|
36
|
-
|
|
34
|
+
try:
|
|
35
|
+
return page.evaluate(js_get_account, csrf_token)
|
|
36
|
+
except:
|
|
37
|
+
return None
|
|
37
38
|
|
|
38
39
|
def login(self, headless=True):
|
|
39
40
|
with sync_playwright() as p:
|
|
40
41
|
browser = p.chromium.launch(headless=headless)
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
|
|
43
|
+
# SEGURANÇA: Só carrega se o arquivo existir
|
|
44
|
+
storage = self.session_path if os.path.exists(self.session_path) else None
|
|
43
45
|
|
|
44
46
|
context = browser.new_context(
|
|
45
47
|
viewport={'width': 1280, 'height': 800},
|
|
@@ -51,15 +53,13 @@ class TriangleClient:
|
|
|
51
53
|
print("[API] Acessando o portal...")
|
|
52
54
|
page.goto("https://www.ctfs.com/content/dash/en/private/Summary.html", timeout=60000)
|
|
53
55
|
|
|
54
|
-
# Se cair no login, faz o processo de autenticação
|
|
55
56
|
if "login" in page.url.lower():
|
|
56
57
|
if not self.username or not self.password:
|
|
57
58
|
browser.close()
|
|
58
59
|
return "NEEDS_CREDENTIALS"
|
|
59
60
|
|
|
60
|
-
print("[API] Sessão expirada. Autenticando...")
|
|
61
|
+
print("[API] Sessão expirada ou inexistente. Autenticando...")
|
|
61
62
|
try:
|
|
62
|
-
# Lida com cookies
|
|
63
63
|
if page.is_visible("#onetrust-accept-btn-handler"):
|
|
64
64
|
page.click("#onetrust-accept-btn-handler", timeout=5000)
|
|
65
65
|
|
|
@@ -69,7 +69,6 @@ class TriangleClient:
|
|
|
69
69
|
page.focus("#password")
|
|
70
70
|
page.keyboard.press("Enter")
|
|
71
71
|
|
|
72
|
-
# Espera sair do login
|
|
73
72
|
page.wait_for_function("() => !window.location.href.includes('login')", timeout=45000)
|
|
74
73
|
|
|
75
74
|
if "challenge" in page.url or "otp" in page.url:
|
|
@@ -82,7 +81,6 @@ class TriangleClient:
|
|
|
82
81
|
browser.close()
|
|
83
82
|
return None
|
|
84
83
|
|
|
85
|
-
# CAPTURA DE DADOS (Saldo)
|
|
86
84
|
print("[API] Capturando dados financeiros...")
|
|
87
85
|
try:
|
|
88
86
|
page.wait_for_load_state("domcontentloaded")
|
|
@@ -91,7 +89,7 @@ class TriangleClient:
|
|
|
91
89
|
|
|
92
90
|
if self.account_data:
|
|
93
91
|
context.storage_state(path=self.session_path)
|
|
94
|
-
print("[API] Dados capturados
|
|
92
|
+
print("[API] Dados capturados e sessão salva.")
|
|
95
93
|
except Exception as e:
|
|
96
94
|
print(f"[AVISO] Erro na captura de saldo: {e}")
|
|
97
95
|
|
|
@@ -99,13 +97,15 @@ class TriangleClient:
|
|
|
99
97
|
return self.account_data
|
|
100
98
|
|
|
101
99
|
def get_transactions(self, start_date=None, headless=True):
|
|
102
|
-
today = datetime.today()
|
|
103
100
|
if not start_date:
|
|
104
|
-
start_date = today.replace(day=1).strftime('%Y-%m-%d')
|
|
101
|
+
start_date = datetime.today().replace(day=1).strftime('%Y-%m-%d')
|
|
105
102
|
|
|
106
103
|
with sync_playwright() as p:
|
|
107
104
|
browser = p.chromium.launch(headless=headless)
|
|
108
|
-
|
|
105
|
+
|
|
106
|
+
# SEGURANÇA: Só carrega se o arquivo existir
|
|
107
|
+
storage = self.session_path if os.path.exists(self.session_path) else None
|
|
108
|
+
context = browser.new_context(storage_state=storage)
|
|
109
109
|
page = context.new_page()
|
|
110
110
|
|
|
111
111
|
print("[API] Sincronizando para transações...")
|
|
@@ -113,11 +113,11 @@ class TriangleClient:
|
|
|
113
113
|
|
|
114
114
|
csrf = self._get_csrf(context)
|
|
115
115
|
|
|
116
|
-
# Se o account_data sumiu da memória, pegamos de novo agora que temos o CSRF
|
|
117
116
|
if not self.account_data:
|
|
118
117
|
self.account_data = self._fetch_account_data(page, csrf)
|
|
119
118
|
|
|
120
119
|
if not self.account_data:
|
|
120
|
+
print("[ERRO] Não foi possível obter dados da conta para transações.")
|
|
121
121
|
browser.close()
|
|
122
122
|
return []
|
|
123
123
|
|
|
@@ -147,7 +147,6 @@ class TriangleClient:
|
|
|
147
147
|
})
|
|
148
148
|
browser.close()
|
|
149
149
|
|
|
150
|
-
# Filtro e Deduplicação
|
|
151
150
|
unique_txs = {}
|
|
152
151
|
for t in raw_txs:
|
|
153
152
|
d = t.get("tranDate", "")
|
|
@@ -167,7 +166,7 @@ class TriangleClient:
|
|
|
167
166
|
"external_id": ext_id, "status": "cleared" if t.get("category") in ["POSTED", "STATEMENTED"] else "uncleared",
|
|
168
167
|
"notes": f"Type: {t.get('type')} | Imported via Triangle API"
|
|
169
168
|
})
|
|
170
|
-
return sorted(normalized, key=lambda x: x
|
|
169
|
+
return sorted(normalized, key=lambda x: x.get("date", ""), reverse=True)
|
|
171
170
|
|
|
172
171
|
def get_balance(self):
|
|
173
172
|
return self.account_data.get("currentBalanceAmt", 0.0) if self.account_data else "N/A"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|