Skip to content

Commit c88dda7

Browse files
author
Derssen
committed
feat: add Make (Integromat) integration
1 parent 4c52268 commit c88dda7

File tree

4 files changed

+74
-33
lines changed

4 files changed

+74
-33
lines changed

README.md

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
# Telegram Balance Monitoring Bot
22

3-
A robust asynchronous Telegram bot designed to monitor financial balances across various API services (Zadarma, DIDWW) and manual subscription services (Wazzup, Streamtele, Callii).
3+
A robust asynchronous Telegram bot designed to monitor financial balances across various API services (**Zadarma**, **DIDWW**, **Make**) and manual subscription services (**Wazzup**, **Streamtele**, **Callii**).
44

5-
The bot runs background scheduled tasks to check for low balances and notifies a specific administrative chat.
5+
The bot runs background scheduled tasks to check for low balances, calculates credit "runway" (burn rate), and notifies a specific administrative chat.
66

77
## Features
88

9-
* **API Integration**: Real-time balance checking for Zadarma and DIDWW.
9+
* **API Integration**:
10+
* **Zadarma & DIDWW**: Real-time balance checking.
11+
* **Make (Integromat)**: Monitoring of "Operations" credits, automatic subscription date synchronization, and advanced "Burn Rate" calculation (predicts if credits will run out before the monthly reset).
1012
* **Manual Tracking**: State management for services without APIs (Wazzup, Callii) via FSM (Finite State Machine).
1113
* **Recurring Payments**: Automatic tracking of monthly subscriptions and daily usage costs.
12-
* **Alerts**:
14+
* **Smart Alerts**:
1315
* Low balance notifications.
14-
* Monthly payment reminders.
16+
* Monthly payment reminders (1 day in advance).
17+
* "Friday Look-ahead": Checks if funds will last through the weekend.
1518
* Daily top-up reminders for high-consumption services.
1619
* **Security**: Restricted access to a specific target chat ID.
1720

@@ -26,7 +29,7 @@ The bot runs background scheduled tasks to check for low balances and notifies a
2629

2730
1. **Clone the repository:**
2831
```bash
29-
git clone <repository-url>
32+
git clone [https://github.com/derssen/telegram-balance-bot.git](https://github.com/derssen/telegram-balance-bot.git)
3033
cd telegram-balance-bot
3134
```
3235

@@ -42,7 +45,29 @@ The bot runs background scheduled tasks to check for low balances and notifies a
4245
```
4346

4447
4. **Configuration:**
45-
Create a `.env` file in the root directory (see `.env.example`).
48+
Create a `.env` file in the root directory:
49+
```env
50+
# Telegram
51+
BOT_TOKEN=your_bot_token
52+
TARGET_CHAT_ID=your_admin_chat_id
53+
54+
# Database
55+
DATABASE_URL=sqlite+aiosqlite:///bot_db.sqlite3
56+
57+
# API Credentials (leave empty to disable service)
58+
ZADARMA_KEY=
59+
ZADARMA_SECRET=
60+
DIDWW_KEY=
61+
62+
# Make (Integromat)
63+
MAKE_API_KEY=your_token
64+
MAKE_ORG_ID=your_org_id
65+
MAKE_ZONE=eu1 # 'eu1' (default) or 'us1'
66+
67+
# Manual Services Costs
68+
WAZZUP_DAILY_COST=400.0
69+
CALLII_DAILY_COST=2.2
70+
```
4671

4772
5. **Run the bot:**
4873
```bash
@@ -55,5 +80,9 @@ The bot runs background scheduled tasks to check for low balances and notifies a
5580
* `config.py`: Configuration and environment variable management.
5681
* `db/`: Database models and initialization.
5782
* `handlers/`: Telegram message and callback handlers.
58-
* `services/`: External API clients.
59-
* `scheduler/`: Background job logic.
83+
* `services/`: External API clients (Zadarma, DIDWW, Make).
84+
* `scheduler/`: Background job logic (API checks, daily deductions, planned alerts).
85+
86+
## License
87+
88+
This project is open-source and available under the MIT License.

config.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,18 @@ class Config:
1212
TARGET_CHAT_ID: int = int(os.getenv("TARGET_CHAT_ID", -1))
1313

1414
# Database
15-
DATABASE_URL: str = os.getenv("DATABASE_URL", "sqlite+aiosqlite:///balances.sqlite3")
15+
DATABASE_URL: str = os.getenv("DATABASE_URL", "sqlite+aiosqlite:///bot_db.sqlite3")
1616

1717
# API Credentials
1818
ZADARMA_KEY: str = os.getenv("ZADARMA_KEY")
1919
ZADARMA_SECRET: str = os.getenv("ZADARMA_SECRET")
2020
WAZZUP_TOKEN: str = os.getenv("WAZZUP_TOKEN")
2121
DIDWW_KEY: str = os.getenv("DIDWW_KEY")
2222

23-
# NEW: Make Credentials
23+
# Make Credentials & Config
2424
MAKE_API_KEY: str = os.getenv("MAKE_API_KEY")
2525
MAKE_ORG_ID: str = os.getenv("MAKE_ORG_ID")
26+
MAKE_ZONE: str = os.getenv("MAKE_ZONE", "eu1") # eu1 or us1
2627

2728
# Financial Constants
2829
LOW_BALANCE_THRESHOLD: float = 10.0
@@ -46,21 +47,21 @@ class Config:
4647
'DIDWW': 'USD',
4748
'Streamtele': 'UAH',
4849
'Callii': 'USD',
49-
'Make': 'Ops', # Operations
50+
'Make': 'Ops',
5051
}
5152

5253
CURRENCY_SIGNS: dict = {
5354
'USD': '$',
5455
'UAH': '₴',
5556
'RUB': '₽',
56-
'Ops': '⚡', # Icon for operations
57+
'Ops': '⚡',
5758
}
5859

59-
# API Service Toggle
60+
# API Service Toggle (Auto-detect based on keys)
6061
API_SERVICE_STATUSES: dict = {
61-
'Zadarma': os.getenv("ZADARMA_ENABLED", "True").lower() in ('true', '1', 't'),
62-
'DIDWW': os.getenv("DIDWW_ENABLED", "True").lower() in ('true', '1', 't'),
63-
'Make': os.getenv("MAKE_ENABLED", "True").lower() in ('true', '1', 't'),
62+
'Zadarma': bool(os.getenv("ZADARMA_KEY") and os.getenv("ZADARMA_SECRET")),
63+
'DIDWW': bool(os.getenv("DIDWW_KEY")),
64+
'Make': bool(os.getenv("MAKE_API_KEY") and os.getenv("MAKE_ORG_ID")),
6465
}
6566

6667
SETTINGS = Config()

requirements.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,6 @@ sqlalchemy>=2.0.0
33
aiohttp
44
python-dotenv
55
aiosqlite
6-
pytz
6+
pytz
7+
greenlet
8+
python-dateutil

services/api_clients.py

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import logging
66
from abc import ABC, abstractmethod
77
from typing import Optional, Dict, Any
8-
from datetime import datetime
98
from config import SETTINGS
109

1110
logger = logging.getLogger(__name__)
@@ -63,7 +62,6 @@ async def get_balance(self) -> Optional[float]:
6362
async with session.get(f"{self.api_url}balance") as response:
6463
if response.status != 200: return None
6564
data = await response.json()
66-
# Safe extraction logic
6765
attrs = {}
6866
if 'data' in data:
6967
d = data['data']
@@ -75,15 +73,14 @@ async def get_balance(self) -> Optional[float]:
7573

7674
class MakeClient(BaseClient):
7775
"""
78-
Client for Make (formerly Integromat) API.
79-
Fetches operation usage and subscription renewal dates.
76+
Client for Make (Integromat) API.
8077
"""
8178
def __init__(self):
8279
self.token = SETTINGS.MAKE_API_KEY
8380
self.org_id = SETTINGS.MAKE_ORG_ID
84-
# Using EU1 by default, but this should ideally match the user's region
85-
self.api_url = f"https://eu1.make.com/api/v2/organizations/{self.org_id}"
86-
self.cached_details = {} # To store details for the advanced check
81+
self.zone = SETTINGS.MAKE_ZONE # eu1 or us1
82+
self.api_url = f"https://{self.zone}.make.com/api/v2/organizations/{self.org_id}"
83+
self.cached_details = {}
8784

8885
async def get_details(self) -> Optional[Dict[str, Any]]:
8986
"""Fetches full license details including usage and reset dates."""
@@ -96,13 +93,22 @@ async def get_details(self) -> Optional[Dict[str, Any]]:
9693
return None
9794

9895
data = await response.json()
99-
license_data = data.get('license', {})
100-
limits = license_data.get('limits', {}).get('operations', {})
10196

97+
# Log removed to reduce noise after fix, but structure is:
98+
# { 'organization': { 'operations': '14851', 'nextReset': '...', 'license': { 'operations': 20000 } } }
99+
100+
org = data.get('organization', {})
101+
license_info = org.get('license', {})
102+
103+
# Parsing logic based on provided logs
104+
usage = float(org.get('operations', 0)) # Value is string in JSON
105+
limit = float(license_info.get('operations', 0)) # Value is int/float
106+
reset_at = org.get('nextReset')
107+
102108
return {
103-
'usage': limits.get('usage', 0),
104-
'limit': limits.get('limit', 0),
105-
'reset_at': license_data.get('resetAt') # ISO String
109+
'usage': usage,
110+
'limit': limit,
111+
'reset_at': reset_at
106112
}
107113
except Exception as e:
108114
logger.error(f"Make API Connection Error: {e}")
@@ -112,13 +118,16 @@ async def get_balance(self) -> Optional[float]:
112118
"""Returns REMAINING operations."""
113119
details = await self.get_details()
114120
if details:
115-
# Cache details for the scheduler to use immediately after
116121
self.cached_details = details
117-
remaining = details['limit'] - details['usage']
122+
123+
limit = details.get('limit', 0)
124+
usage = details.get('usage', 0)
125+
remaining = limit - usage
126+
127+
logger.info(f"Make Calc: Limit {limit} - Usage {usage} = {remaining}")
118128
return float(remaining)
119129
return None
120130

121-
# Service Registry
122131
API_CLIENTS = {
123132
'Zadarma': ZadarmaClient(),
124133
'DIDWW': DIDWWClient(),

0 commit comments

Comments
 (0)