triangle-api 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.
- triangle_api-0.1.0/PKG-INFO +106 -0
- triangle_api-0.1.0/README.md +83 -0
- triangle_api-0.1.0/setup.cfg +4 -0
- triangle_api-0.1.0/setup.py +25 -0
- triangle_api-0.1.0/triangle_api/__init__.py +4 -0
- triangle_api-0.1.0/triangle_api/client.py +218 -0
- triangle_api-0.1.0/triangle_api/exceptions.py +0 -0
- triangle_api-0.1.0/triangle_api.egg-info/PKG-INFO +106 -0
- triangle_api-0.1.0/triangle_api.egg-info/SOURCES.txt +10 -0
- triangle_api-0.1.0/triangle_api.egg-info/dependency_links.txt +1 -0
- triangle_api-0.1.0/triangle_api.egg-info/requires.txt +1 -0
- triangle_api-0.1.0/triangle_api.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: triangle-api
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Unofficial Python wrapper for Canadian Tire Triangle Mastercard API
|
|
5
|
+
Home-page: https://github.com/diogobas/triangle-api
|
|
6
|
+
Author: Diogo Bastos
|
|
7
|
+
Author-email: seu-email@exemplo.com
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.8
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Requires-Dist: playwright>=1.40.0
|
|
14
|
+
Dynamic: author
|
|
15
|
+
Dynamic: author-email
|
|
16
|
+
Dynamic: classifier
|
|
17
|
+
Dynamic: description
|
|
18
|
+
Dynamic: description-content-type
|
|
19
|
+
Dynamic: home-page
|
|
20
|
+
Dynamic: requires-dist
|
|
21
|
+
Dynamic: requires-python
|
|
22
|
+
Dynamic: summary
|
|
23
|
+
|
|
24
|
+
# Triangle Mastercard API (Unofficial) 🇨🇦
|
|
25
|
+
|
|
26
|
+
[](https://www.python.org/downloads/)
|
|
27
|
+
[](https://opensource.org/licenses/MIT)
|
|
28
|
+
[](https://github.com/diogobas/triangle-api/graphs/commit-activity)
|
|
29
|
+
|
|
30
|
+
Um wrapper em Python para a API privada do **Triangle Mastercard (Canadian Tire Financial Services)**.
|
|
31
|
+
|
|
32
|
+
Este projeto realiza a engenharia reversa do portal web oficial para extrair informações de saldo, limites e transações, permitindo a integração dos seus dados financeiros em seus próprios aplicativos, bots ou planilhas.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## ⚠️ Aviso Legal (Disclaimer)
|
|
37
|
+
|
|
38
|
+
**Este projeto não é afiliado, mantido, autorizado, endossado ou patrocinado pela Canadian Tire Financial Services (CTFS) ou qualquer uma de suas afiliadas.**
|
|
39
|
+
|
|
40
|
+
Este é um software independente para fins educacionais e de uso pessoal. O uso de automação para acessar contas bancárias pode violar os Termos de Serviço da instituição. Use com responsabilidade e por sua conta e risco.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## ✨ Funcionalidades
|
|
45
|
+
|
|
46
|
+
- [x] **Autenticação Automatizada**: Realiza o login via Playwright lidando com proteção de bots.
|
|
47
|
+
- [x] **Suporte a MFA**: Detecta e solicita o código de SMS/Segurança via terminal.
|
|
48
|
+
- [x] **Persistência de Sessão**: Salva cookies localmente para evitar logins constantes e reduzir o risco de bloqueio.
|
|
49
|
+
- [x] **Resumo da Conta**: Saldo atual, crédito disponível, limite total e data de vencimento.
|
|
50
|
+
- [ ] **Histórico de Transações**: (Em desenvolvimento)
|
|
51
|
+
- [ ] **Triangle Rewards**: Saldo de Canadian Tire Money (Em desenvolvimento)
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 🚀 Instalação
|
|
56
|
+
|
|
57
|
+
1. Clone o repositório:
|
|
58
|
+
`git clone https://github.com/diogobas/triangle-api.git
|
|
59
|
+
cd triangle-api`
|
|
60
|
+
|
|
61
|
+
2. Instale as dependências: `pip install -r requirements.txt`
|
|
62
|
+
|
|
63
|
+
3. Instale o navegador necessário pelo Playwright: `playwright install chromium`
|
|
64
|
+
|
|
65
|
+
💻 Exemplo de Uso
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
from triangle_api.client
|
|
69
|
+
import TriangleClient
|
|
70
|
+
import os
|
|
71
|
+
|
|
72
|
+
# Recomenda-se usar variáveis de ambiente para segurança
|
|
73
|
+
USER = "seu_email@exemplo.com"
|
|
74
|
+
PASSWORD = "sua_senha_secreta"
|
|
75
|
+
|
|
76
|
+
# Instancia o cliente
|
|
77
|
+
client = TriangleClient(username=USER, password=PASSWORD)
|
|
78
|
+
|
|
79
|
+
# Realiza o login (abre o navegador na primeira vez, usa cookies nas próximas)
|
|
80
|
+
# headless=False permite ver o navegador operando
|
|
81
|
+
data = client.login(headless=True)
|
|
82
|
+
|
|
83
|
+
print(f"Saldo Atual: $ {client.get_balance()}")
|
|
84
|
+
print(f"Crédito Disponível: $ {data.get('availableCreditAmt')}")
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
🔒 Segurança e Persistência
|
|
88
|
+
|
|
89
|
+
Para evitar que o banco suspeite de múltiplos logins, este wrapper utiliza o
|
|
90
|
+
recurso storage_state do Playwright.
|
|
91
|
+
|
|
92
|
+
- Após o primeiro login bem-sucedido, um arquivo auth_state.json é criado.
|
|
93
|
+
- Nas próximas execuções, o script tentará usar esses cookies para acessar o
|
|
94
|
+
Dashboard diretamente.
|
|
95
|
+
- Atenção: Nunca envie o arquivo auth_state.json para o GitHub (ele já está no
|
|
96
|
+
.gitignore).
|
|
97
|
+
|
|
98
|
+
🛠️ Desenvolvimento
|
|
99
|
+
|
|
100
|
+
Se você quiser contribuir para o projeto mapeando novos endpoints (como
|
|
101
|
+
transações ou ofertas), siga estes passos:
|
|
102
|
+
|
|
103
|
+
1. Faça o login no site da CTFS com o modo Inspecionar (F12) aberto.
|
|
104
|
+
2. Filtre por chamadas XHR/Fetch.
|
|
105
|
+
3. Procure por endpoints como retrieveTransactions ou retrieveLoyalty.
|
|
106
|
+
4. Mapeie o JSON de resposta na classe TriangleClient.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Triangle Mastercard API (Unofficial) 🇨🇦
|
|
2
|
+
|
|
3
|
+
[](https://www.python.org/downloads/)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://github.com/diogobas/triangle-api/graphs/commit-activity)
|
|
6
|
+
|
|
7
|
+
Um wrapper em Python para a API privada do **Triangle Mastercard (Canadian Tire Financial Services)**.
|
|
8
|
+
|
|
9
|
+
Este projeto realiza a engenharia reversa do portal web oficial para extrair informações de saldo, limites e transações, permitindo a integração dos seus dados financeiros em seus próprios aplicativos, bots ou planilhas.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## ⚠️ Aviso Legal (Disclaimer)
|
|
14
|
+
|
|
15
|
+
**Este projeto não é afiliado, mantido, autorizado, endossado ou patrocinado pela Canadian Tire Financial Services (CTFS) ou qualquer uma de suas afiliadas.**
|
|
16
|
+
|
|
17
|
+
Este é um software independente para fins educacionais e de uso pessoal. O uso de automação para acessar contas bancárias pode violar os Termos de Serviço da instituição. Use com responsabilidade e por sua conta e risco.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## ✨ Funcionalidades
|
|
22
|
+
|
|
23
|
+
- [x] **Autenticação Automatizada**: Realiza o login via Playwright lidando com proteção de bots.
|
|
24
|
+
- [x] **Suporte a MFA**: Detecta e solicita o código de SMS/Segurança via terminal.
|
|
25
|
+
- [x] **Persistência de Sessão**: Salva cookies localmente para evitar logins constantes e reduzir o risco de bloqueio.
|
|
26
|
+
- [x] **Resumo da Conta**: Saldo atual, crédito disponível, limite total e data de vencimento.
|
|
27
|
+
- [ ] **Histórico de Transações**: (Em desenvolvimento)
|
|
28
|
+
- [ ] **Triangle Rewards**: Saldo de Canadian Tire Money (Em desenvolvimento)
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 🚀 Instalação
|
|
33
|
+
|
|
34
|
+
1. Clone o repositório:
|
|
35
|
+
`git clone https://github.com/diogobas/triangle-api.git
|
|
36
|
+
cd triangle-api`
|
|
37
|
+
|
|
38
|
+
2. Instale as dependências: `pip install -r requirements.txt`
|
|
39
|
+
|
|
40
|
+
3. Instale o navegador necessário pelo Playwright: `playwright install chromium`
|
|
41
|
+
|
|
42
|
+
💻 Exemplo de Uso
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
from triangle_api.client
|
|
46
|
+
import TriangleClient
|
|
47
|
+
import os
|
|
48
|
+
|
|
49
|
+
# Recomenda-se usar variáveis de ambiente para segurança
|
|
50
|
+
USER = "seu_email@exemplo.com"
|
|
51
|
+
PASSWORD = "sua_senha_secreta"
|
|
52
|
+
|
|
53
|
+
# Instancia o cliente
|
|
54
|
+
client = TriangleClient(username=USER, password=PASSWORD)
|
|
55
|
+
|
|
56
|
+
# Realiza o login (abre o navegador na primeira vez, usa cookies nas próximas)
|
|
57
|
+
# headless=False permite ver o navegador operando
|
|
58
|
+
data = client.login(headless=True)
|
|
59
|
+
|
|
60
|
+
print(f"Saldo Atual: $ {client.get_balance()}")
|
|
61
|
+
print(f"Crédito Disponível: $ {data.get('availableCreditAmt')}")
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
🔒 Segurança e Persistência
|
|
65
|
+
|
|
66
|
+
Para evitar que o banco suspeite de múltiplos logins, este wrapper utiliza o
|
|
67
|
+
recurso storage_state do Playwright.
|
|
68
|
+
|
|
69
|
+
- Após o primeiro login bem-sucedido, um arquivo auth_state.json é criado.
|
|
70
|
+
- Nas próximas execuções, o script tentará usar esses cookies para acessar o
|
|
71
|
+
Dashboard diretamente.
|
|
72
|
+
- Atenção: Nunca envie o arquivo auth_state.json para o GitHub (ele já está no
|
|
73
|
+
.gitignore).
|
|
74
|
+
|
|
75
|
+
🛠️ Desenvolvimento
|
|
76
|
+
|
|
77
|
+
Se você quiser contribuir para o projeto mapeando novos endpoints (como
|
|
78
|
+
transações ou ofertas), siga estes passos:
|
|
79
|
+
|
|
80
|
+
1. Faça o login no site da CTFS com o modo Inspecionar (F12) aberto.
|
|
81
|
+
2. Filtre por chamadas XHR/Fetch.
|
|
82
|
+
3. Procure por endpoints como retrieveTransactions ou retrieveLoyalty.
|
|
83
|
+
4. Mapeie o JSON de resposta na classe TriangleClient.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
with open("README.md", "r", encoding="utf-8") as fh:
|
|
4
|
+
long_description = fh.read()
|
|
5
|
+
|
|
6
|
+
setup(
|
|
7
|
+
name="triangle-api", # Nome que aparecerá no PyPI
|
|
8
|
+
version="0.1.0",
|
|
9
|
+
author="Diogo Bastos",
|
|
10
|
+
author_email="seu-email@exemplo.com", # Opcional
|
|
11
|
+
description="Unofficial Python wrapper for Canadian Tire Triangle Mastercard API",
|
|
12
|
+
long_description=long_description,
|
|
13
|
+
long_description_content_type="text/markdown",
|
|
14
|
+
url="https://github.com/diogobas/triangle-api",
|
|
15
|
+
packages=find_packages(),
|
|
16
|
+
classifiers=[
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
],
|
|
21
|
+
python_requires='>=3.8',
|
|
22
|
+
install_requires=[
|
|
23
|
+
"playwright>=1.40.0",
|
|
24
|
+
],
|
|
25
|
+
)
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
from playwright.sync_api import sync_playwright
|
|
2
|
+
import os
|
|
3
|
+
import time
|
|
4
|
+
import hashlib
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
|
|
7
|
+
class TriangleClient:
|
|
8
|
+
def __init__(self, username=None, password=None, session_path="auth_state.json"):
|
|
9
|
+
self.username = username
|
|
10
|
+
self.password = password
|
|
11
|
+
self.session_path = session_path
|
|
12
|
+
self.account_data = None
|
|
13
|
+
|
|
14
|
+
def login(self, headless=True):
|
|
15
|
+
with sync_playwright() as p:
|
|
16
|
+
# 1. Configuração do Navegador
|
|
17
|
+
browser = p.chromium.launch(headless=headless)
|
|
18
|
+
|
|
19
|
+
# Verifica se existe sessão salva para tentar restaurar
|
|
20
|
+
has_session = os.path.exists(self.session_path)
|
|
21
|
+
storage = self.session_path if has_session else None
|
|
22
|
+
|
|
23
|
+
context = browser.new_context(
|
|
24
|
+
viewport={'width': 1280, 'height': 800},
|
|
25
|
+
storage_state=storage,
|
|
26
|
+
user_agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
|
27
|
+
)
|
|
28
|
+
page = context.new_page()
|
|
29
|
+
|
|
30
|
+
# 2. Definição da URL inicial
|
|
31
|
+
if has_session:
|
|
32
|
+
print("[API] Tentando restaurar sessão anterior...")
|
|
33
|
+
target_url = "https://www.ctfs.com/content/dash/en/private/Summary.html"
|
|
34
|
+
else:
|
|
35
|
+
print("[API] Iniciando novo login...")
|
|
36
|
+
target_url = "https://www.ctfs.com/content/dashpublic/en/login.html"
|
|
37
|
+
|
|
38
|
+
page.goto(target_url, wait_until="load", timeout=60000)
|
|
39
|
+
|
|
40
|
+
# 3. Lidar com Banner de Cookies (OneTrust)
|
|
41
|
+
# Esse banner bloqueia a interação com os campos de texto se não for fechado
|
|
42
|
+
try:
|
|
43
|
+
cookie_btn = page.locator("#onetrust-accept-btn-handler, text='Accept All'")
|
|
44
|
+
if cookie_btn.is_visible(timeout=8000):
|
|
45
|
+
cookie_btn.click()
|
|
46
|
+
print("[API] Banner de cookies aceito.")
|
|
47
|
+
time.sleep(1) # Pausa para animação de fechamento
|
|
48
|
+
except:
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
# 4. Fluxo de Autenticação (Caso a sessão tenha expirado ou não exista)
|
|
52
|
+
if "login" in page.url.lower() or not has_session:
|
|
53
|
+
if not self.username or not self.password:
|
|
54
|
+
print("[API] Credenciais necessárias para continuar.")
|
|
55
|
+
browser.close()
|
|
56
|
+
return "NEEDS_CREDENTIALS"
|
|
57
|
+
|
|
58
|
+
print("[API] Tela de login detectada. Preenchendo credenciais...")
|
|
59
|
+
try:
|
|
60
|
+
page.wait_for_selector("#username", timeout=30000)
|
|
61
|
+
page.fill("#username", self.username)
|
|
62
|
+
page.fill("#password", self.password)
|
|
63
|
+
|
|
64
|
+
# Submissão via Teclado (Enter) é mais resiliente contra widgets de chat
|
|
65
|
+
page.focus("#password")
|
|
66
|
+
page.keyboard.press("Enter")
|
|
67
|
+
|
|
68
|
+
# Clique de segurança via JavaScript caso o Enter falhe
|
|
69
|
+
page.evaluate("document.querySelector('#signin-form')?.click()")
|
|
70
|
+
|
|
71
|
+
# Espera sair da tela de login (mudar URL ou detectar MFA)
|
|
72
|
+
print("[API] Formulário enviado. Aguardando resposta do banco...")
|
|
73
|
+
page.wait_for_function("() => !window.location.href.includes('login')", timeout=45000)
|
|
74
|
+
|
|
75
|
+
# --- Verificação de MFA (SMS/OTP) ---
|
|
76
|
+
if "challenge" in page.url or "otp" in page.url or page.is_visible("input[aria-describedby*='otp']"):
|
|
77
|
+
print("\n[MFA] Verificação de segurança detectada.")
|
|
78
|
+
sms_code = input("Digite o código SMS recebido no celular: ")
|
|
79
|
+
page.get_by_role("textbox").first.fill(sms_code)
|
|
80
|
+
page.keyboard.press("Enter")
|
|
81
|
+
page.wait_for_url("**/Summary.html", timeout=60000)
|
|
82
|
+
|
|
83
|
+
print("[API] Login realizado com sucesso!")
|
|
84
|
+
|
|
85
|
+
except Exception as e:
|
|
86
|
+
print(f"[ERRO] Falha no processo de login: {e}")
|
|
87
|
+
page.screenshot(path="debug_login_error.png")
|
|
88
|
+
browser.close()
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
# 5. Captura Final de Dados (Saldo e transientReference)
|
|
92
|
+
# Fazemos um reload forçado para "pescar" o JSON do saldo atualizado
|
|
93
|
+
print("[API] Capturando dados financeiros (retrieveAccount)...")
|
|
94
|
+
try:
|
|
95
|
+
# Esperamos a página estabilizar antes de recarregar
|
|
96
|
+
page.wait_for_load_state("domcontentloaded")
|
|
97
|
+
|
|
98
|
+
with page.expect_response("**/retrieveAccount", timeout=30000) as resp:
|
|
99
|
+
page.goto("https://www.ctfs.com/content/dash/en/private/Summary.html")
|
|
100
|
+
|
|
101
|
+
self.account_data = resp.value.json()
|
|
102
|
+
|
|
103
|
+
# Salva os cookies/sessão somente após confirmar a captura dos dados
|
|
104
|
+
if self.account_data:
|
|
105
|
+
context.storage_state(path=self.session_path)
|
|
106
|
+
print("[API] Sessão e dados financeiros salvos.")
|
|
107
|
+
|
|
108
|
+
except Exception as e:
|
|
109
|
+
print(f"[AVISO] Não foi possível capturar os dados via rede: {e}")
|
|
110
|
+
|
|
111
|
+
browser.close()
|
|
112
|
+
return self.account_data
|
|
113
|
+
|
|
114
|
+
def get_normalized_transactions(self, start_date=None, headless=True):
|
|
115
|
+
"""
|
|
116
|
+
Versão ultra-robusta para evitar erros de ordenação (NoneType).
|
|
117
|
+
"""
|
|
118
|
+
# 1. Busca as transações brutas
|
|
119
|
+
raw_txs = self.get_transactions(start_date=start_date, headless=headless)
|
|
120
|
+
|
|
121
|
+
normalized = []
|
|
122
|
+
seen_ids = set()
|
|
123
|
+
|
|
124
|
+
for tx in raw_txs:
|
|
125
|
+
# Pegamos a data. Se não existir, ignoramos o item.
|
|
126
|
+
t_date = tx.get("tranDate")
|
|
127
|
+
if not t_date:
|
|
128
|
+
continue
|
|
129
|
+
|
|
130
|
+
payee = tx.get("merchant", "Unknown Merchant")
|
|
131
|
+
amount = float(tx.get("amount", 0.0))
|
|
132
|
+
|
|
133
|
+
# Gerar ID Único para deduplicação
|
|
134
|
+
identifier = f"{t_date}|{payee}|{amount}"
|
|
135
|
+
if identifier in seen_ids:
|
|
136
|
+
continue
|
|
137
|
+
seen_ids.add(identifier)
|
|
138
|
+
|
|
139
|
+
# MD5 para o Lunch Money
|
|
140
|
+
ext_id = hashlib.md5(identifier.encode("utf-8")).hexdigest()
|
|
141
|
+
|
|
142
|
+
normalized.append({
|
|
143
|
+
"date": t_date,
|
|
144
|
+
"payee": payee,
|
|
145
|
+
"amount": amount,
|
|
146
|
+
"currency": "cad",
|
|
147
|
+
"external_id": ext_id,
|
|
148
|
+
"type": tx.get("type", "PURCHASE"),
|
|
149
|
+
"status": "cleared" if tx.get("category") in ["POSTED", "STATEMENTED"] else "uncleared"
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
# Ordenar garantindo que não existam Nones na chave de ordenação
|
|
153
|
+
return sorted(normalized, key=lambda x: x.get("date", ""), reverse=True)
|
|
154
|
+
|
|
155
|
+
def get_transactions(self, start_date=None, headless=True):
|
|
156
|
+
today = datetime.today()
|
|
157
|
+
if not start_date:
|
|
158
|
+
start_date = today.replace(day=1).strftime('%Y-%m-%d')
|
|
159
|
+
end_date = today.strftime('%Y-%m-%d')
|
|
160
|
+
|
|
161
|
+
print(f"[API] Buscando transações de {start_date} até {end_date}...")
|
|
162
|
+
|
|
163
|
+
with sync_playwright() as p:
|
|
164
|
+
browser = p.chromium.launch(headless=headless)
|
|
165
|
+
storage = self.session_path if os.path.exists(self.session_path) else None
|
|
166
|
+
context = browser.new_context(storage_state=storage)
|
|
167
|
+
page = context.new_page()
|
|
168
|
+
|
|
169
|
+
try:
|
|
170
|
+
print("[API] Sincronizando sessão para histórico...")
|
|
171
|
+
# Navega e pesca o retrieveAccount da janela atual
|
|
172
|
+
with page.expect_response("**/retrieveAccount", timeout=30000) as resp_acc:
|
|
173
|
+
page.goto("https://www.ctfs.com/content/dash/en/private/Summary.html", wait_until="domcontentloaded")
|
|
174
|
+
|
|
175
|
+
acc_info = resp_acc.value.json()
|
|
176
|
+
transient = acc_info.get("transientReference")
|
|
177
|
+
acc_id = acc_info.get("accountId")
|
|
178
|
+
last_stmt = acc_info.get("lastStatementDate")
|
|
179
|
+
|
|
180
|
+
cookies = context.cookies()
|
|
181
|
+
csrf = next((c['value'] for c in cookies if c['name'] == 'csrftoken'), "")
|
|
182
|
+
|
|
183
|
+
# Script de busca tripla
|
|
184
|
+
js_script = """
|
|
185
|
+
async (params) => {
|
|
186
|
+
const call = async (cat, stmt = null) => {
|
|
187
|
+
const body = { category: cat, transientReference: params.transient, accountId: params.accId };
|
|
188
|
+
if (stmt) body.statementDate = stmt;
|
|
189
|
+
const r = await fetch('/dash/v1/account/retrieveTransactions', {
|
|
190
|
+
method: 'POST',
|
|
191
|
+
headers: { 'Content-Type': 'application/json', 'CSRF-Token': params.csrf, 'csrfToken': params.csrf },
|
|
192
|
+
body: JSON.stringify(body)
|
|
193
|
+
});
|
|
194
|
+
return r.ok ? await r.json() : { transactions: [] };
|
|
195
|
+
};
|
|
196
|
+
const auth = await call('AUTHORIZED');
|
|
197
|
+
const post = await call('POSTED');
|
|
198
|
+
const stmt = params.lastStmt ? await call('STATEMENTED', params.lastStmt) : { transactions: [] };
|
|
199
|
+
return [...(auth.transactions || []), ...(post.transactions || []), ...(stmt.transactions || [])];
|
|
200
|
+
}
|
|
201
|
+
"""
|
|
202
|
+
|
|
203
|
+
raw_txs = page.evaluate(js_script, {
|
|
204
|
+
"transient": transient, "accId": acc_id, "lastStmt": last_stmt, "csrf": csrf
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
# Filtro de data bruto
|
|
208
|
+
filtered = [t for t in raw_txs if t.get("tranDate", "") >= start_date]
|
|
209
|
+
browser.close()
|
|
210
|
+
return filtered
|
|
211
|
+
|
|
212
|
+
except Exception as e:
|
|
213
|
+
print(f"[ERRO] Falha na captura: {e}")
|
|
214
|
+
browser.close()
|
|
215
|
+
return []
|
|
216
|
+
|
|
217
|
+
def get_balance(self):
|
|
218
|
+
return self.account_data.get("currentBalanceAmt", 0.0) if self.account_data else "N/A"
|
|
File without changes
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: triangle-api
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Unofficial Python wrapper for Canadian Tire Triangle Mastercard API
|
|
5
|
+
Home-page: https://github.com/diogobas/triangle-api
|
|
6
|
+
Author: Diogo Bastos
|
|
7
|
+
Author-email: seu-email@exemplo.com
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.8
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Requires-Dist: playwright>=1.40.0
|
|
14
|
+
Dynamic: author
|
|
15
|
+
Dynamic: author-email
|
|
16
|
+
Dynamic: classifier
|
|
17
|
+
Dynamic: description
|
|
18
|
+
Dynamic: description-content-type
|
|
19
|
+
Dynamic: home-page
|
|
20
|
+
Dynamic: requires-dist
|
|
21
|
+
Dynamic: requires-python
|
|
22
|
+
Dynamic: summary
|
|
23
|
+
|
|
24
|
+
# Triangle Mastercard API (Unofficial) 🇨🇦
|
|
25
|
+
|
|
26
|
+
[](https://www.python.org/downloads/)
|
|
27
|
+
[](https://opensource.org/licenses/MIT)
|
|
28
|
+
[](https://github.com/diogobas/triangle-api/graphs/commit-activity)
|
|
29
|
+
|
|
30
|
+
Um wrapper em Python para a API privada do **Triangle Mastercard (Canadian Tire Financial Services)**.
|
|
31
|
+
|
|
32
|
+
Este projeto realiza a engenharia reversa do portal web oficial para extrair informações de saldo, limites e transações, permitindo a integração dos seus dados financeiros em seus próprios aplicativos, bots ou planilhas.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## ⚠️ Aviso Legal (Disclaimer)
|
|
37
|
+
|
|
38
|
+
**Este projeto não é afiliado, mantido, autorizado, endossado ou patrocinado pela Canadian Tire Financial Services (CTFS) ou qualquer uma de suas afiliadas.**
|
|
39
|
+
|
|
40
|
+
Este é um software independente para fins educacionais e de uso pessoal. O uso de automação para acessar contas bancárias pode violar os Termos de Serviço da instituição. Use com responsabilidade e por sua conta e risco.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## ✨ Funcionalidades
|
|
45
|
+
|
|
46
|
+
- [x] **Autenticação Automatizada**: Realiza o login via Playwright lidando com proteção de bots.
|
|
47
|
+
- [x] **Suporte a MFA**: Detecta e solicita o código de SMS/Segurança via terminal.
|
|
48
|
+
- [x] **Persistência de Sessão**: Salva cookies localmente para evitar logins constantes e reduzir o risco de bloqueio.
|
|
49
|
+
- [x] **Resumo da Conta**: Saldo atual, crédito disponível, limite total e data de vencimento.
|
|
50
|
+
- [ ] **Histórico de Transações**: (Em desenvolvimento)
|
|
51
|
+
- [ ] **Triangle Rewards**: Saldo de Canadian Tire Money (Em desenvolvimento)
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 🚀 Instalação
|
|
56
|
+
|
|
57
|
+
1. Clone o repositório:
|
|
58
|
+
`git clone https://github.com/diogobas/triangle-api.git
|
|
59
|
+
cd triangle-api`
|
|
60
|
+
|
|
61
|
+
2. Instale as dependências: `pip install -r requirements.txt`
|
|
62
|
+
|
|
63
|
+
3. Instale o navegador necessário pelo Playwright: `playwright install chromium`
|
|
64
|
+
|
|
65
|
+
💻 Exemplo de Uso
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
from triangle_api.client
|
|
69
|
+
import TriangleClient
|
|
70
|
+
import os
|
|
71
|
+
|
|
72
|
+
# Recomenda-se usar variáveis de ambiente para segurança
|
|
73
|
+
USER = "seu_email@exemplo.com"
|
|
74
|
+
PASSWORD = "sua_senha_secreta"
|
|
75
|
+
|
|
76
|
+
# Instancia o cliente
|
|
77
|
+
client = TriangleClient(username=USER, password=PASSWORD)
|
|
78
|
+
|
|
79
|
+
# Realiza o login (abre o navegador na primeira vez, usa cookies nas próximas)
|
|
80
|
+
# headless=False permite ver o navegador operando
|
|
81
|
+
data = client.login(headless=True)
|
|
82
|
+
|
|
83
|
+
print(f"Saldo Atual: $ {client.get_balance()}")
|
|
84
|
+
print(f"Crédito Disponível: $ {data.get('availableCreditAmt')}")
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
🔒 Segurança e Persistência
|
|
88
|
+
|
|
89
|
+
Para evitar que o banco suspeite de múltiplos logins, este wrapper utiliza o
|
|
90
|
+
recurso storage_state do Playwright.
|
|
91
|
+
|
|
92
|
+
- Após o primeiro login bem-sucedido, um arquivo auth_state.json é criado.
|
|
93
|
+
- Nas próximas execuções, o script tentará usar esses cookies para acessar o
|
|
94
|
+
Dashboard diretamente.
|
|
95
|
+
- Atenção: Nunca envie o arquivo auth_state.json para o GitHub (ele já está no
|
|
96
|
+
.gitignore).
|
|
97
|
+
|
|
98
|
+
🛠️ Desenvolvimento
|
|
99
|
+
|
|
100
|
+
Se você quiser contribuir para o projeto mapeando novos endpoints (como
|
|
101
|
+
transações ou ofertas), siga estes passos:
|
|
102
|
+
|
|
103
|
+
1. Faça o login no site da CTFS com o modo Inspecionar (F12) aberto.
|
|
104
|
+
2. Filtre por chamadas XHR/Fetch.
|
|
105
|
+
3. Procure por endpoints como retrieveTransactions ou retrieveLoyalty.
|
|
106
|
+
4. Mapeie o JSON de resposta na classe TriangleClient.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
setup.py
|
|
3
|
+
triangle_api/__init__.py
|
|
4
|
+
triangle_api/client.py
|
|
5
|
+
triangle_api/exceptions.py
|
|
6
|
+
triangle_api.egg-info/PKG-INFO
|
|
7
|
+
triangle_api.egg-info/SOURCES.txt
|
|
8
|
+
triangle_api.egg-info/dependency_links.txt
|
|
9
|
+
triangle_api.egg-info/requires.txt
|
|
10
|
+
triangle_api.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
playwright>=1.40.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
triangle_api
|