expense-bot-source.zip у цьому ж inbox. Містить:
Не включено: .env, credentials.json, Expense_Tracker.xlsx, deploy.bat, __pycache__
python-telegram-bot==21.8
google-genai>=1.0.0
gspread>=6.0.0
google-auth>=2.0.0
python-dotenv>=1.0.0
cryptography>=43.0.0
config.py читає зашифровані значення з .env і розшифровує через Fernet з MASTER_PASSWORD:
.env keys:
TELEGRAM_BOT_TOKEN_ENC=<fernet-encrypted>
GEMINI_API_KEY_ENC=<fernet-encrypted>
GOOGLE_SHEETS_ID_ENC=<fernet-encrypted>
GOOGLE_CREDENTIALS_PATH=credentials.json
ALLOWED_USER_IDS=<telegram user id>
Runtime (не в .env, передається окремо):
MASTER_PASSWORD=<пароль>
На VPS секрети захищені systemd EnvironmentFile (chmod 600), шар crypto непотрібний.
Замінити config.py на простий варіант:
import os
TELEGRAM_BOT_TOKEN = os.environ["TELEGRAM_BOT_TOKEN"]
GEMINI_API_KEY = os.environ["GEMINI_API_KEY"]
GOOGLE_SHEETS_ID = os.environ["GOOGLE_SHEETS_ID"]
GOOGLE_CREDENTIALS_PATH = os.environ.get("GOOGLE_CREDENTIALS_PATH", "/srv/services/expense-bot/credentials.json")
ALLOWED_USER_IDS = [int(x) for x in os.environ.get("ALLOWED_USER_IDS", "").split(",") if x.strip()]
CATEGORIES = [
"Food", "Transport", "Housing", "Utilities",
"Health", "Entertainment", "Clothing",
"Education", "Subscriptions", "Other",
]
EXPENSES_WORKSHEET = "Expenses"
Тоді secrets.env у passepartout — стандартний KEY=value.
crypto_utils.py можна залишити (не шкодить), просто не використовувати.
python bot.py
(або /srv/services/expense-bot/.venv/bin/python bot.py у systemd unit)
Файл: expense-bot@expense-bot-492610.iam.gserviceaccount.com
IP restriction: GCP Service Account keys за замовчуванням НЕ прив'язані до IP.
IP-обмеження можливі лише через VPC Service Controls або Org Policy — у звичайному проекті їх немає.
Той самий credentials.json працюватиме з VPS IP 31.131.26.203 без змін.
Де взяти файл на VPS: credentials.json не потрапив у zip (secret). Варіанти:
scp credentials.json root@31.131.26.203:/srv/passepartout/expense-bot/Після копіювання у systemd unit додати:
EnvironmentFile=/srv/passepartout/expense-bot/secrets.env
Environment=GOOGLE_CREDENTIALS_PATH=/srv/passepartout/expense-bot/credentials.json
Файл expense_parser.py, модель gemini-2.5-flash, thinking=0.
You extract expense data from user messages in Ukrainian or Russian.
Return ONLY valid JSON, no other text.
Available categories: {CATEGORIES}
JSON schema:
{"date": "YYYY-MM-DD", "amount": float, "currency": "UAH",
"category": "...", "comment": "short description in user's language"}
Rules:
- If no date: use today
- If no currency: default to UAH
- amount must be positive
- Pick best matching category
- comment: brief description of what was purchased
Застосовується до text / voice / photo однаково (системний prompt + контентний блок). Нова 2-рівнева таксономія — просто замінити список CATEGORIES і оновити системний промпт.
Worksheet: Expenses
Колонки: Date | Amount | Currency | Category | Comment | Timestamp
/report команда → get_monthly_totals(YYYY-MM) → агрегація по Category.
credentials.json на VPS (або сгенерувати новий SA ключ)secrets.env у passepartout містить plaintext значення (або encrypted + MASTER_PASSWORD)