@cachly-dev/mcp-server 0.10.56 → 0.10.57
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.
- package/.github/workflows/eas-build-android.yml +58 -0
- package/.github/workflows/eas-update.yml +55 -0
- package/.github/workflows/publish.yml +2 -2
- package/BENCH.md +91 -0
- package/CHANGELOG.md +44 -0
- package/README.md +6 -34
- package/STRATEGY.md +228 -0
- package/apps/travel-chaos-organizer/.env.example +49 -0
- package/apps/travel-chaos-organizer/DEPLOY.md +328 -0
- package/apps/travel-chaos-organizer/Makefile +57 -0
- package/apps/travel-chaos-organizer/README.md +159 -0
- package/apps/travel-chaos-organizer/backend/.env.example +10 -0
- package/apps/travel-chaos-organizer/backend/Dockerfile +25 -0
- package/apps/travel-chaos-organizer/backend/alembic/env.py +56 -0
- package/apps/travel-chaos-organizer/backend/alembic/script.py.mako +24 -0
- package/apps/travel-chaos-organizer/backend/alembic/versions/.gitkeep +0 -0
- package/apps/travel-chaos-organizer/backend/alembic/versions/0001_stripe_customer_id.py +24 -0
- package/apps/travel-chaos-organizer/backend/alembic/versions/0002_telegram_chat_id.py +24 -0
- package/apps/travel-chaos-organizer/backend/alembic.ini +39 -0
- package/apps/travel-chaos-organizer/backend/app/__init__.py +0 -0
- package/apps/travel-chaos-organizer/backend/app/auth/__init__.py +0 -0
- package/apps/travel-chaos-organizer/backend/app/auth/keycloak.py +81 -0
- package/apps/travel-chaos-organizer/backend/app/config.py +66 -0
- package/apps/travel-chaos-organizer/backend/app/db/__init__.py +0 -0
- package/apps/travel-chaos-organizer/backend/app/db/database.py +20 -0
- package/apps/travel-chaos-organizer/backend/app/db/migrations/001_init.sql +71 -0
- package/apps/travel-chaos-organizer/backend/app/db/migrations/002_telemetry_freemium.sql +31 -0
- package/apps/travel-chaos-organizer/backend/app/db/migrations/003_telegram_chat_id.sql +4 -0
- package/apps/travel-chaos-organizer/backend/app/db/migrations/004_stripe_and_email.sql +5 -0
- package/apps/travel-chaos-organizer/backend/app/limiter.py +4 -0
- package/apps/travel-chaos-organizer/backend/app/logging_config.py +25 -0
- package/apps/travel-chaos-organizer/backend/app/main.py +77 -0
- package/apps/travel-chaos-organizer/backend/app/models/__init__.py +0 -0
- package/apps/travel-chaos-organizer/backend/app/models/schemas.py +142 -0
- package/apps/travel-chaos-organizer/backend/app/routers/__init__.py +0 -0
- package/apps/travel-chaos-organizer/backend/app/routers/admin.py +237 -0
- package/apps/travel-chaos-organizer/backend/app/routers/cache.py +13 -0
- package/apps/travel-chaos-organizer/backend/app/routers/events.py +29 -0
- package/apps/travel-chaos-organizer/backend/app/routers/export.py +122 -0
- package/apps/travel-chaos-organizer/backend/app/routers/inbox.py +122 -0
- package/apps/travel-chaos-organizer/backend/app/routers/items.py +130 -0
- package/apps/travel-chaos-organizer/backend/app/routers/mail.py +108 -0
- package/apps/travel-chaos-organizer/backend/app/routers/parse.py +303 -0
- package/apps/travel-chaos-organizer/backend/app/routers/payments.py +179 -0
- package/apps/travel-chaos-organizer/backend/app/routers/trips.py +134 -0
- package/apps/travel-chaos-organizer/backend/app/routers/users.py +169 -0
- package/apps/travel-chaos-organizer/backend/app/routers/waitlist.py +37 -0
- package/apps/travel-chaos-organizer/backend/app/services/__init__.py +0 -0
- package/apps/travel-chaos-organizer/backend/app/services/cache.py +131 -0
- package/apps/travel-chaos-organizer/backend/app/services/drip.py +128 -0
- package/apps/travel-chaos-organizer/backend/app/services/email.py +61 -0
- package/apps/travel-chaos-organizer/backend/app/services/errors.py +44 -0
- package/apps/travel-chaos-organizer/backend/app/services/notifier.py +10 -0
- package/apps/travel-chaos-organizer/backend/app/services/ollama.py +101 -0
- package/apps/travel-chaos-organizer/backend/app/services/parser.py +22 -0
- package/apps/travel-chaos-organizer/backend/app/services/quota.py +85 -0
- package/apps/travel-chaos-organizer/backend/app/services/sentry.py +11 -0
- package/apps/travel-chaos-organizer/backend/app/services/telemetry.py +35 -0
- package/apps/travel-chaos-organizer/backend/pytest.ini +4 -0
- package/apps/travel-chaos-organizer/backend/requirements-dev.txt +6 -0
- package/apps/travel-chaos-organizer/backend/requirements.txt +25 -0
- package/apps/travel-chaos-organizer/backend/tests/__init__.py +0 -0
- package/apps/travel-chaos-organizer/backend/tests/conftest.py +223 -0
- package/apps/travel-chaos-organizer/backend/tests/test_admin.py +99 -0
- package/apps/travel-chaos-organizer/backend/tests/test_cache.py +142 -0
- package/apps/travel-chaos-organizer/backend/tests/test_drip.py +32 -0
- package/apps/travel-chaos-organizer/backend/tests/test_e2e_chain.py +121 -0
- package/apps/travel-chaos-organizer/backend/tests/test_export.py +48 -0
- package/apps/travel-chaos-organizer/backend/tests/test_inbox.py +107 -0
- package/apps/travel-chaos-organizer/backend/tests/test_items.py +138 -0
- package/apps/travel-chaos-organizer/backend/tests/test_mail.py +113 -0
- package/apps/travel-chaos-organizer/backend/tests/test_ollama.py +145 -0
- package/apps/travel-chaos-organizer/backend/tests/test_parse_url.py +98 -0
- package/apps/travel-chaos-organizer/backend/tests/test_parser.py +72 -0
- package/apps/travel-chaos-organizer/backend/tests/test_payments.py +163 -0
- package/apps/travel-chaos-organizer/backend/tests/test_quota.py +46 -0
- package/apps/travel-chaos-organizer/backend/tests/test_search.py +67 -0
- package/apps/travel-chaos-organizer/backend/tests/test_trips.py +123 -0
- package/apps/travel-chaos-organizer/backend/tests/test_users.py +52 -0
- package/apps/travel-chaos-organizer/backups/.gitkeep +0 -0
- package/apps/travel-chaos-organizer/caddy/Caddyfile +27 -0
- package/apps/travel-chaos-organizer/docker-compose.node1.yml +31 -0
- package/apps/travel-chaos-organizer/docker-compose.override.yml +14 -0
- package/apps/travel-chaos-organizer/docker-compose.yml +128 -0
- package/apps/travel-chaos-organizer/frontend/.env.example +13 -0
- package/apps/travel-chaos-organizer/frontend/app/(app)/_layout.tsx +80 -0
- package/apps/travel-chaos-organizer/frontend/app/(app)/inbox.tsx +166 -0
- package/apps/travel-chaos-organizer/frontend/app/(app)/search.tsx +130 -0
- package/apps/travel-chaos-organizer/frontend/app/(app)/settings.tsx +381 -0
- package/apps/travel-chaos-organizer/frontend/app/(app)/trips/[id].tsx +346 -0
- package/apps/travel-chaos-organizer/frontend/app/(app)/trips.tsx +363 -0
- package/apps/travel-chaos-organizer/frontend/app/(app)/upgrade/cancel.tsx +34 -0
- package/apps/travel-chaos-organizer/frontend/app/(app)/upgrade/success.tsx +35 -0
- package/apps/travel-chaos-organizer/frontend/app/(auth)/login.tsx +91 -0
- package/apps/travel-chaos-organizer/frontend/app/_layout.tsx +71 -0
- package/apps/travel-chaos-organizer/frontend/app/auth.tsx +29 -0
- package/apps/travel-chaos-organizer/frontend/app/index.tsx +16 -0
- package/apps/travel-chaos-organizer/frontend/app/onboarding.tsx +119 -0
- package/apps/travel-chaos-organizer/frontend/app.config.ts +54 -0
- package/apps/travel-chaos-organizer/frontend/assets/adaptive-icon.svg +66 -0
- package/apps/travel-chaos-organizer/frontend/assets/icon.svg +79 -0
- package/apps/travel-chaos-organizer/frontend/assets/splash.svg +15 -0
- package/apps/travel-chaos-organizer/frontend/components/BottomSheet.tsx +102 -0
- package/apps/travel-chaos-organizer/frontend/components/ConfettiCannon.tsx +107 -0
- package/apps/travel-chaos-organizer/frontend/components/ConfirmDialog.tsx +109 -0
- package/apps/travel-chaos-organizer/frontend/components/ErrorBoundary.tsx +45 -0
- package/apps/travel-chaos-organizer/frontend/components/FileUploadButton.tsx +19 -0
- package/apps/travel-chaos-organizer/frontend/components/ImportSheet.tsx +155 -0
- package/apps/travel-chaos-organizer/frontend/components/OfflineBadge.tsx +55 -0
- package/apps/travel-chaos-organizer/frontend/components/QRCodeViewer.tsx +80 -0
- package/apps/travel-chaos-organizer/frontend/components/ScoreRing.tsx +39 -0
- package/apps/travel-chaos-organizer/frontend/components/Screen.tsx +39 -0
- package/apps/travel-chaos-organizer/frontend/components/Skeleton.tsx +70 -0
- package/apps/travel-chaos-organizer/frontend/components/SwipeableInboxItem.tsx +121 -0
- package/apps/travel-chaos-organizer/frontend/components/SwipeableTimelineItem.tsx +70 -0
- package/apps/travel-chaos-organizer/frontend/components/TimelineItem.tsx +73 -0
- package/apps/travel-chaos-organizer/frontend/components/TimelineItemDetail.tsx +130 -0
- package/apps/travel-chaos-organizer/frontend/components/Toast.tsx +60 -0
- package/apps/travel-chaos-organizer/frontend/components/ToastContext.tsx +42 -0
- package/apps/travel-chaos-organizer/frontend/components/TripCard.tsx +146 -0
- package/apps/travel-chaos-organizer/frontend/components/TripQRModal.tsx +70 -0
- package/apps/travel-chaos-organizer/frontend/components/UpgradeBanner.tsx +44 -0
- package/apps/travel-chaos-organizer/frontend/eas.json +22 -0
- package/apps/travel-chaos-organizer/frontend/hooks/useInbox.ts +24 -0
- package/apps/travel-chaos-organizer/frontend/hooks/useNetworkSync.ts +60 -0
- package/apps/travel-chaos-organizer/frontend/hooks/useTrips.ts +112 -0
- package/apps/travel-chaos-organizer/frontend/lib/api.ts +223 -0
- package/apps/travel-chaos-organizer/frontend/lib/auth.ts +93 -0
- package/apps/travel-chaos-organizer/frontend/lib/db.ts +86 -0
- package/apps/travel-chaos-organizer/frontend/lib/fileCache.ts +54 -0
- package/apps/travel-chaos-organizer/frontend/lib/haptics.ts +13 -0
- package/apps/travel-chaos-organizer/frontend/lib/notifications.ts +112 -0
- package/apps/travel-chaos-organizer/frontend/lib/offlineQueue.ts +145 -0
- package/apps/travel-chaos-organizer/frontend/lib/quota.tsx +43 -0
- package/apps/travel-chaos-organizer/frontend/lib/sentry.ts +28 -0
- package/apps/travel-chaos-organizer/frontend/lib/shareIntent.ts +44 -0
- package/apps/travel-chaos-organizer/frontend/package-lock.json +13221 -0
- package/apps/travel-chaos-organizer/frontend/package.json +44 -0
- package/apps/travel-chaos-organizer/frontend/tsconfig.json +23 -0
- package/apps/travel-chaos-organizer/keycloak/realm-export.json +59 -0
- package/apps/travel-chaos-organizer/landing/index.html +773 -0
- package/apps/travel-chaos-organizer/scripts/backup.sh +14 -0
- package/apps/travel-chaos-organizer/scripts/configure-env.sh +82 -0
- package/apps/travel-chaos-organizer/scripts/deploy.sh +54 -0
- package/apps/travel-chaos-organizer/scripts/health-check.sh +36 -0
- package/apps/travel-chaos-organizer/scripts/migrate.sh +7 -0
- package/apps/travel-chaos-organizer/scripts/server-setup.sh +85 -0
- package/apps/travel-chaos-organizer/scripts/smoke-test.sh +29 -0
- package/apps/travel-chaos-organizer/scripts/verify-tracking.sh +71 -0
- package/apps/travel-chaos-organizer/telegram-bot/.env.example +3 -0
- package/apps/travel-chaos-organizer/telegram-bot/Dockerfile +6 -0
- package/apps/travel-chaos-organizer/telegram-bot/bot.py +504 -0
- package/apps/travel-chaos-organizer/telegram-bot/requirements.txt +3 -0
- package/dist/packages/telegram-notify/client.d.ts +19 -0
- package/dist/packages/telegram-notify/client.js +75 -0
- package/dist/packages/telegram-notify/client.js.map +1 -0
- package/dist/src/__tests__/auth.test.js.map +1 -0
- package/dist/{__tests__ → src/__tests__}/brain-flow.test.js +141 -0
- package/dist/src/__tests__/brain-flow.test.js.map +1 -0
- package/dist/src/__tests__/confidence.test.js.map +1 -0
- package/dist/src/__tests__/e2e/api-auth.test.js.map +1 -0
- package/dist/src/__tests__/e2e/api-health.test.js.map +1 -0
- package/dist/src/__tests__/e2e/cli.test.js.map +1 -0
- package/dist/src/__tests__/e2e/config.js.map +1 -0
- package/dist/src/__tests__/e2e/funnel.test.js.map +1 -0
- package/dist/src/__tests__/e2e/mcp-tools.test.js.map +1 -0
- package/dist/src/__tests__/e2e/provisioning.test.js.map +1 -0
- package/dist/{__tests__ → src/__tests__}/handlers-unit.test.js +0 -2
- package/dist/src/__tests__/handlers-unit.test.js.map +1 -0
- package/dist/src/__tests__/namespace.test.js.map +1 -0
- package/dist/src/__tests__/rerank.test.js +115 -0
- package/dist/src/__tests__/rerank.test.js.map +1 -0
- package/dist/src/auth.js.map +1 -0
- package/dist/src/bench/cachly-bench.d.ts +35 -0
- package/dist/src/bench/cachly-bench.js +182 -0
- package/dist/src/bench/cachly-bench.js.map +1 -0
- package/dist/src/bench/fixtures.d.ts +37 -0
- package/dist/src/bench/fixtures.js +225 -0
- package/dist/src/bench/fixtures.js.map +1 -0
- package/dist/src/ckg.js.map +1 -0
- package/dist/src/confidence.js.map +1 -0
- package/dist/src/embeddings.js.map +1 -0
- package/dist/src/handlers/advanced.js.map +1 -0
- package/dist/{handlers → src/handlers}/brain.js +335 -193
- package/dist/src/handlers/brain.js.map +1 -0
- package/dist/src/handlers/cache.js.map +1 -0
- package/dist/{handlers → src/handlers}/context.js +9 -4
- package/dist/src/handlers/context.js.map +1 -0
- package/dist/{handlers → src/handlers}/fedbrain.d.ts +6 -0
- package/dist/{handlers → src/handlers}/fedbrain.js +19 -7
- package/dist/src/handlers/fedbrain.js.map +1 -0
- package/dist/src/handlers/instances.js.map +1 -0
- package/dist/{handlers → src/handlers}/roadmap.js +7 -3
- package/dist/src/handlers/roadmap.js.map +1 -0
- package/dist/src/handlers/syndicate.js.map +1 -0
- package/dist/src/handlers/tco.d.ts +16 -0
- package/dist/src/handlers/tco.js +303 -0
- package/dist/src/handlers/tco.js.map +1 -0
- package/dist/{handlers → src/handlers}/team.js +57 -7
- package/dist/src/handlers/team.js.map +1 -0
- package/dist/{index.js → src/index.js} +209 -431
- package/dist/src/index.js.map +1 -0
- package/dist/src/namespace.js.map +1 -0
- package/dist/src/notifier.d.ts +5 -0
- package/dist/src/notifier.js +6 -0
- package/dist/src/notifier.js.map +1 -0
- package/dist/src/rerank.d.ts +55 -0
- package/dist/src/rerank.js +111 -0
- package/dist/src/rerank.js.map +1 -0
- package/dist/src/search.js.map +1 -0
- package/dist/src/search.test.d.ts +1 -0
- package/dist/src/search.test.js.map +1 -0
- package/dist/{tools.d.ts → src/tools.d.ts} +226 -0
- package/dist/{tools.js → src/tools.js} +196 -0
- package/dist/src/tools.js.map +1 -0
- package/dist/src/utils.js.map +1 -0
- package/package.json +8 -7
- package/packages/telegram-notify/SPLIT_PLAN.md +106 -0
- package/packages/telegram-notify/client.ts +88 -0
- package/packages/telegram-notify/emojis.json +22 -0
- package/packages/telegram-notify/pyproject.toml +17 -0
- package/packages/telegram-notify/telegram_notify/__init__.py +3 -0
- package/packages/telegram-notify/telegram_notify/client.py +87 -0
- package/packages/telegram-notify/telegram_notify/emojis.json +22 -0
- package/scripts/generate-icons.js +28 -0
- package/server.json +3 -3
- package/smithery.yaml +27 -16
- package/src/__tests__/brain-flow.test.ts +174 -0
- package/src/__tests__/handlers-unit.test.ts +1 -2
- package/src/__tests__/rerank.test.ts +130 -0
- package/src/bench/cachly-bench.ts +217 -0
- package/src/bench/fixtures.ts +256 -0
- package/src/handlers/brain.ts +345 -197
- package/src/handlers/context.ts +9 -4
- package/src/handlers/fedbrain.ts +21 -7
- package/src/handlers/roadmap.ts +7 -3
- package/src/handlers/tco.ts +347 -0
- package/src/handlers/team.ts +67 -7
- package/src/index.ts +224 -418
- package/src/notifier.ts +5 -0
- package/src/rerank.ts +148 -0
- package/src/tools.ts +210 -0
- package/tsconfig.json +2 -3
- package/dist/__tests__/auth.test.js.map +0 -1
- package/dist/__tests__/brain-flow.test.js.map +0 -1
- package/dist/__tests__/brain-predict-failures.test.d.ts +0 -17
- package/dist/__tests__/brain-predict-failures.test.js +0 -339
- package/dist/__tests__/brain-predict-failures.test.js.map +0 -1
- package/dist/__tests__/confidence.test.js.map +0 -1
- package/dist/__tests__/e2e/api-auth.test.js.map +0 -1
- package/dist/__tests__/e2e/api-health.test.js.map +0 -1
- package/dist/__tests__/e2e/cli.test.js.map +0 -1
- package/dist/__tests__/e2e/config.js.map +0 -1
- package/dist/__tests__/e2e/funnel.test.js.map +0 -1
- package/dist/__tests__/e2e/mcp-tools.test.js.map +0 -1
- package/dist/__tests__/e2e/provisioning.test.js.map +0 -1
- package/dist/__tests__/handlers-unit.test.js.map +0 -1
- package/dist/__tests__/namespace.test.js.map +0 -1
- package/dist/auth.js.map +0 -1
- package/dist/ckg.js.map +0 -1
- package/dist/confidence.js.map +0 -1
- package/dist/embeddings.js.map +0 -1
- package/dist/handlers/advanced.js.map +0 -1
- package/dist/handlers/brain.js.map +0 -1
- package/dist/handlers/cache.js.map +0 -1
- package/dist/handlers/context.js.map +0 -1
- package/dist/handlers/fedbrain.js.map +0 -1
- package/dist/handlers/instances.js.map +0 -1
- package/dist/handlers/roadmap.js.map +0 -1
- package/dist/handlers/syndicate.js.map +0 -1
- package/dist/handlers/team.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/namespace.js.map +0 -1
- package/dist/search.js.map +0 -1
- package/dist/search.test.js.map +0 -1
- package/dist/tools.js.map +0 -1
- package/dist/utils.js.map +0 -1
- package/src/__tests__/brain-predict-failures.test.ts +0 -430
- /package/dist/{__tests__ → src/__tests__}/auth.test.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/auth.test.js +0 -0
- /package/dist/{__tests__ → src/__tests__}/brain-flow.test.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/confidence.test.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/confidence.test.js +0 -0
- /package/dist/{__tests__ → src/__tests__}/e2e/api-auth.test.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/e2e/api-auth.test.js +0 -0
- /package/dist/{__tests__ → src/__tests__}/e2e/api-health.test.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/e2e/api-health.test.js +0 -0
- /package/dist/{__tests__ → src/__tests__}/e2e/cli.test.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/e2e/cli.test.js +0 -0
- /package/dist/{__tests__ → src/__tests__}/e2e/config.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/e2e/config.js +0 -0
- /package/dist/{__tests__ → src/__tests__}/e2e/funnel.test.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/e2e/funnel.test.js +0 -0
- /package/dist/{__tests__ → src/__tests__}/e2e/mcp-tools.test.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/e2e/mcp-tools.test.js +0 -0
- /package/dist/{__tests__ → src/__tests__}/e2e/provisioning.test.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/e2e/provisioning.test.js +0 -0
- /package/dist/{__tests__ → src/__tests__}/handlers-unit.test.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/namespace.test.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/namespace.test.js +0 -0
- /package/dist/{search.test.d.ts → src/__tests__/rerank.test.d.ts} +0 -0
- /package/dist/{auth.d.ts → src/auth.d.ts} +0 -0
- /package/dist/{auth.js → src/auth.js} +0 -0
- /package/dist/{ckg.d.ts → src/ckg.d.ts} +0 -0
- /package/dist/{ckg.js → src/ckg.js} +0 -0
- /package/dist/{confidence.d.ts → src/confidence.d.ts} +0 -0
- /package/dist/{confidence.js → src/confidence.js} +0 -0
- /package/dist/{embeddings.d.ts → src/embeddings.d.ts} +0 -0
- /package/dist/{embeddings.js → src/embeddings.js} +0 -0
- /package/dist/{handlers → src/handlers}/advanced.d.ts +0 -0
- /package/dist/{handlers → src/handlers}/advanced.js +0 -0
- /package/dist/{handlers → src/handlers}/brain.d.ts +0 -0
- /package/dist/{handlers → src/handlers}/cache.d.ts +0 -0
- /package/dist/{handlers → src/handlers}/cache.js +0 -0
- /package/dist/{handlers → src/handlers}/context.d.ts +0 -0
- /package/dist/{handlers → src/handlers}/instances.d.ts +0 -0
- /package/dist/{handlers → src/handlers}/instances.js +0 -0
- /package/dist/{handlers → src/handlers}/roadmap.d.ts +0 -0
- /package/dist/{handlers → src/handlers}/syndicate.d.ts +0 -0
- /package/dist/{handlers → src/handlers}/syndicate.js +0 -0
- /package/dist/{handlers → src/handlers}/team.d.ts +0 -0
- /package/dist/{index.d.ts → src/index.d.ts} +0 -0
- /package/dist/{namespace.d.ts → src/namespace.d.ts} +0 -0
- /package/dist/{namespace.js → src/namespace.js} +0 -0
- /package/dist/{search.d.ts → src/search.d.ts} +0 -0
- /package/dist/{search.js → src/search.js} +0 -0
- /package/dist/{search.test.js → src/search.test.js} +0 -0
- /package/dist/{utils.d.ts → src/utils.d.ts} +0 -0
- /package/dist/{utils.js → src/utils.js} +0 -0
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TCO Telegram Bot
|
|
3
|
+
Commands: /start /link /unlink /trips /next /inbox /status /help
|
|
4
|
+
Forward any document/image/text → parsed via TCO AI
|
|
5
|
+
Inline keyboards for trip assignment
|
|
6
|
+
"""
|
|
7
|
+
import logging
|
|
8
|
+
import os
|
|
9
|
+
from dotenv import load_dotenv
|
|
10
|
+
import httpx
|
|
11
|
+
from telegram import (
|
|
12
|
+
Update, InlineKeyboardButton, InlineKeyboardMarkup,
|
|
13
|
+
)
|
|
14
|
+
from telegram.ext import (
|
|
15
|
+
Application, CallbackQueryHandler, CommandHandler,
|
|
16
|
+
ContextTypes, MessageHandler, filters,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
load_dotenv()
|
|
20
|
+
TOKEN = os.environ["TELEGRAM_BOT_TOKEN"]
|
|
21
|
+
API_URL = os.environ.get("TCO_API_URL", "http://localhost:8000")
|
|
22
|
+
BOT_SECRET = os.environ.get("TCO_BOT_SECRET", TOKEN)
|
|
23
|
+
|
|
24
|
+
logging.basicConfig(level=logging.INFO)
|
|
25
|
+
log = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
_ICONS = {
|
|
28
|
+
"flight": "✈️", "train": "🚂", "bus": "🚌", "hotel": "🏨",
|
|
29
|
+
"rental_car": "🚗", "activity": "🎡", "transfer": "🚕",
|
|
30
|
+
"document": "📄", "other": "📋",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# ── Auth helpers ────────────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
def _bot_headers(user_id: str | None = None) -> dict:
|
|
37
|
+
h = {"X-Bot-Token": BOT_SECRET}
|
|
38
|
+
if user_id:
|
|
39
|
+
h["X-User-Id"] = user_id
|
|
40
|
+
return h
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
async def get_linked_user(chat_id: str) -> dict | None:
|
|
44
|
+
async with httpx.AsyncClient(timeout=10) as c:
|
|
45
|
+
r = await c.get(
|
|
46
|
+
f"{API_URL}/api/v1/users/by-telegram/{chat_id}",
|
|
47
|
+
headers=_bot_headers(),
|
|
48
|
+
)
|
|
49
|
+
if r.status_code == 200:
|
|
50
|
+
return r.json()
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
async def _require_link(update: Update) -> dict | None:
|
|
55
|
+
"""Return linked user or send prompt and return None."""
|
|
56
|
+
user = await get_linked_user(str(update.effective_chat.id))
|
|
57
|
+
if not user:
|
|
58
|
+
await update.message.reply_text(
|
|
59
|
+
"🔗 Bitte zuerst Konto verknüpfen:\n\n"
|
|
60
|
+
"1. TCO-App → Einstellungen → *Mit Telegram verknüpfen*\n"
|
|
61
|
+
"2. Sende mir den PIN: `/link 123456`",
|
|
62
|
+
parse_mode="Markdown",
|
|
63
|
+
)
|
|
64
|
+
return user
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# ── Commands ────────────────────────────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
async def cmd_start(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
|
70
|
+
chat_id = str(update.effective_chat.id)
|
|
71
|
+
user = await get_linked_user(chat_id)
|
|
72
|
+
if user:
|
|
73
|
+
await update.message.reply_text(
|
|
74
|
+
f"👋 Willkommen zurück!\n\n"
|
|
75
|
+
f"Plan: *{'✦ Pro' if user.get('plan') == 'pro' else 'Free'}*\n\n"
|
|
76
|
+
f"/trips — Reisen anzeigen\n/next — Nächstes Event\n/inbox — Chaos Inbox",
|
|
77
|
+
parse_mode="Markdown",
|
|
78
|
+
)
|
|
79
|
+
else:
|
|
80
|
+
await update.message.reply_text(
|
|
81
|
+
"✈️ *Travel Chaos Organizer Bot*\n\n"
|
|
82
|
+
"Ich helfe dir, deine Reisedokumente zu verwalten.\n\n"
|
|
83
|
+
"*Erste Schritte:*\n"
|
|
84
|
+
"1. Öffne die TCO-App → Einstellungen → Telegram verknüpfen\n"
|
|
85
|
+
"2. Du bekommst einen 6-stelligen PIN\n"
|
|
86
|
+
"3. Schreib mir: `/link 123456`\n\n"
|
|
87
|
+
"Danach kannst du mir Buchungsbestätigungen, Screenshots oder PDFs schicken!",
|
|
88
|
+
parse_mode="Markdown",
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
async def cmd_help(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
|
93
|
+
await update.message.reply_text(
|
|
94
|
+
"*TCO Bot — Befehle:*\n\n"
|
|
95
|
+
"/trips — Deine Reisen\n"
|
|
96
|
+
"/next — Nächstes Event\n"
|
|
97
|
+
"/inbox — Chaos Inbox\n"
|
|
98
|
+
"/status — Kontostatus\n"
|
|
99
|
+
"/link \\<PIN\\> — Konto verknüpfen\n"
|
|
100
|
+
"/unlink — Verknüpfung aufheben\n"
|
|
101
|
+
"/help — Diese Hilfe\n\n"
|
|
102
|
+
"*Dokumente senden:*\n"
|
|
103
|
+
"Einfach Buchungsbestätigung, PDF oder Screenshot schicken — wird automatisch mit KI geparst ✨\n\n"
|
|
104
|
+
"*Text senden:*\n"
|
|
105
|
+
"Buchungsbestätigung als Text einfügen und senden.",
|
|
106
|
+
parse_mode="Markdown",
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
async def cmd_link(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
|
111
|
+
if not ctx.args:
|
|
112
|
+
await update.message.reply_text("Bitte PIN angeben: `/link 123456`", parse_mode="Markdown")
|
|
113
|
+
return
|
|
114
|
+
pin = ctx.args[0].strip()
|
|
115
|
+
chat_id = str(update.effective_chat.id)
|
|
116
|
+
async with httpx.AsyncClient(timeout=10) as c:
|
|
117
|
+
r = await c.post(
|
|
118
|
+
f"{API_URL}/api/v1/users/telegram-link",
|
|
119
|
+
params={"pin": pin, "chat_id": chat_id},
|
|
120
|
+
)
|
|
121
|
+
if r.status_code == 200:
|
|
122
|
+
await update.message.reply_text(
|
|
123
|
+
"✅ *Konto erfolgreich verknüpft!*\n\n"
|
|
124
|
+
"Du kannst jetzt:\n"
|
|
125
|
+
"• Buchungsbestätigungen oder PDFs schicken → werden automatisch geparst\n"
|
|
126
|
+
"• /trips — Reisen anzeigen\n"
|
|
127
|
+
"• /inbox — Chaos Inbox verwalten\n"
|
|
128
|
+
"• /next — Nächstes Event anzeigen",
|
|
129
|
+
parse_mode="Markdown",
|
|
130
|
+
)
|
|
131
|
+
elif r.status_code == 410:
|
|
132
|
+
await update.message.reply_text("⏰ PIN abgelaufen. Bitte neuen PIN in der App generieren.")
|
|
133
|
+
elif r.status_code == 404:
|
|
134
|
+
await update.message.reply_text("❌ PIN nicht gefunden. Bitte prüfe die Eingabe.")
|
|
135
|
+
else:
|
|
136
|
+
await update.message.reply_text("⚠️ Fehler beim Verknüpfen. Bitte versuche es erneut.")
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
async def cmd_unlink(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
|
140
|
+
user = await _require_link(update)
|
|
141
|
+
if not user:
|
|
142
|
+
return
|
|
143
|
+
keyboard = InlineKeyboardMarkup([[
|
|
144
|
+
InlineKeyboardButton("✅ Ja, trennen", callback_data="unlink:confirm"),
|
|
145
|
+
InlineKeyboardButton("❌ Abbrechen", callback_data="unlink:cancel"),
|
|
146
|
+
]])
|
|
147
|
+
await update.message.reply_text(
|
|
148
|
+
"⚠️ Telegram-Verknüpfung aufheben?\n\nDu erhältst dann keine Bot-Nachrichten mehr.",
|
|
149
|
+
reply_markup=keyboard,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
async def cmd_status(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
|
154
|
+
chat_id = str(update.effective_chat.id)
|
|
155
|
+
user = await get_linked_user(chat_id)
|
|
156
|
+
if not user:
|
|
157
|
+
await update.message.reply_text(
|
|
158
|
+
"🔴 Konto *nicht verknüpft*\n\nNutze `/link <PIN>` zum Verbinden.",
|
|
159
|
+
parse_mode="Markdown",
|
|
160
|
+
)
|
|
161
|
+
return
|
|
162
|
+
plan = user.get("plan", "free")
|
|
163
|
+
plan_label = "✦ Pro" if plan == "pro" else "Free"
|
|
164
|
+
await update.message.reply_text(
|
|
165
|
+
f"✅ *Konto verknüpft*\n\nPlan: *{plan_label}*\n\nTelegramm-Chat-ID: `{chat_id}`",
|
|
166
|
+
parse_mode="Markdown",
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
async def cmd_trips(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
|
171
|
+
user = await _require_link(update)
|
|
172
|
+
if not user:
|
|
173
|
+
return
|
|
174
|
+
async with httpx.AsyncClient(timeout=10) as c:
|
|
175
|
+
r = await c.get(f"{API_URL}/api/v1/trips", headers=_bot_headers(user["id"]))
|
|
176
|
+
if r.status_code != 200:
|
|
177
|
+
await update.message.reply_text("⚠️ Fehler beim Laden der Trips.")
|
|
178
|
+
return
|
|
179
|
+
trips = r.json()
|
|
180
|
+
if not trips:
|
|
181
|
+
await update.message.reply_text("📭 Noch keine Trips. Erstelle deinen ersten Trip in der TCO-App!")
|
|
182
|
+
return
|
|
183
|
+
# Build inline keyboard — each trip is a button to view its timeline
|
|
184
|
+
buttons = [
|
|
185
|
+
[InlineKeyboardButton(
|
|
186
|
+
f"{'✈️ ' if t.get('start_date') else '📋 '}{t['name']}",
|
|
187
|
+
callback_data=f"trip:{t['id']}:{t['name'][:20]}",
|
|
188
|
+
)]
|
|
189
|
+
for t in trips[:8]
|
|
190
|
+
]
|
|
191
|
+
await update.message.reply_text(
|
|
192
|
+
f"🗺️ *Deine Trips ({len(trips)}):*",
|
|
193
|
+
reply_markup=InlineKeyboardMarkup(buttons),
|
|
194
|
+
parse_mode="Markdown",
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
async def cmd_next(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
|
199
|
+
user = await _require_link(update)
|
|
200
|
+
if not user:
|
|
201
|
+
return
|
|
202
|
+
async with httpx.AsyncClient(timeout=10) as c:
|
|
203
|
+
r = await c.get(f"{API_URL}/api/v1/trips", headers=_bot_headers(user["id"]))
|
|
204
|
+
if r.status_code != 200 or not r.json():
|
|
205
|
+
await update.message.reply_text("Keine Trips gefunden.")
|
|
206
|
+
return
|
|
207
|
+
from datetime import datetime, timezone
|
|
208
|
+
today = datetime.now(timezone.utc).date().isoformat()
|
|
209
|
+
upcoming = [t for t in r.json() if t.get("start_date") and t["start_date"] >= today]
|
|
210
|
+
if not upcoming:
|
|
211
|
+
await update.message.reply_text("📭 Kein bevorstehender Trip gefunden.")
|
|
212
|
+
return
|
|
213
|
+
t = upcoming[0]
|
|
214
|
+
start = t.get("start_date", "?")[:10]
|
|
215
|
+
end = t.get("end_date", "?")[:10] if t.get("end_date") else "?"
|
|
216
|
+
days = ""
|
|
217
|
+
try:
|
|
218
|
+
from datetime import date
|
|
219
|
+
d = (date.fromisoformat(start) - date.today()).days
|
|
220
|
+
days = f" (in {d} {'Tag' if d == 1 else 'Tagen'})"
|
|
221
|
+
except Exception:
|
|
222
|
+
pass
|
|
223
|
+
keyboard = InlineKeyboardMarkup([[
|
|
224
|
+
InlineKeyboardButton("📅 Timeline anzeigen", callback_data=f"trip:{t['id']}:{t['name'][:20]}"),
|
|
225
|
+
]])
|
|
226
|
+
await update.message.reply_text(
|
|
227
|
+
f"🗓️ *Nächster Trip:*\n\n*{t['name']}*\nStart: {start}{days}\nEnde: {end}",
|
|
228
|
+
reply_markup=keyboard,
|
|
229
|
+
parse_mode="Markdown",
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
async def cmd_inbox(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
|
234
|
+
user = await _require_link(update)
|
|
235
|
+
if not user:
|
|
236
|
+
return
|
|
237
|
+
async with httpx.AsyncClient(timeout=10) as c:
|
|
238
|
+
r = await c.get(
|
|
239
|
+
f"{API_URL}/api/v1/inbox?status_filter=pending",
|
|
240
|
+
headers=_bot_headers(user["id"]),
|
|
241
|
+
)
|
|
242
|
+
if r.status_code != 200:
|
|
243
|
+
await update.message.reply_text("⚠️ Fehler beim Laden des Inbox.")
|
|
244
|
+
return
|
|
245
|
+
items = r.json()
|
|
246
|
+
if not items:
|
|
247
|
+
await update.message.reply_text("🎉 Chaos Inbox ist leer!")
|
|
248
|
+
return
|
|
249
|
+
# Show first 5 items with assign/reject buttons
|
|
250
|
+
for item in items[:5]:
|
|
251
|
+
pd = item.get("parsed_data") or {}
|
|
252
|
+
title = pd.get("title") or "(kein Titel)"
|
|
253
|
+
itype = pd.get("type") or "other"
|
|
254
|
+
icon = _ICONS.get(itype, "📋")
|
|
255
|
+
provider = pd.get("provider") or ""
|
|
256
|
+
ref = pd.get("booking_ref") or ""
|
|
257
|
+
lines = [f"{icon} *{title}*"]
|
|
258
|
+
if provider:
|
|
259
|
+
lines.append(f"Anbieter: {provider}")
|
|
260
|
+
if ref:
|
|
261
|
+
lines.append(f"Ref: `{ref}`")
|
|
262
|
+
keyboard = InlineKeyboardMarkup([[
|
|
263
|
+
InlineKeyboardButton("✈️ Trip zuweisen", callback_data=f"assign:{item['id']}:pick"),
|
|
264
|
+
InlineKeyboardButton("🗑️ Löschen", callback_data=f"reject:{item['id']}"),
|
|
265
|
+
]])
|
|
266
|
+
await update.message.reply_text(
|
|
267
|
+
"\n".join(lines),
|
|
268
|
+
reply_markup=keyboard,
|
|
269
|
+
parse_mode="Markdown",
|
|
270
|
+
)
|
|
271
|
+
if len(items) > 5:
|
|
272
|
+
await update.message.reply_text(f"… und {len(items) - 5} weitere. Öffne die App für die vollständige Ansicht.")
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
# ── Callback query handlers ─────────────────────────────────────────────────────
|
|
276
|
+
|
|
277
|
+
async def handle_callback(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
|
278
|
+
query = update.callback_query
|
|
279
|
+
await query.answer()
|
|
280
|
+
data = query.data or ""
|
|
281
|
+
chat_id = str(update.effective_chat.id)
|
|
282
|
+
user = await get_linked_user(chat_id)
|
|
283
|
+
if not user:
|
|
284
|
+
await query.edit_message_text("⚠️ Konto nicht verknüpft. Nutze /link.")
|
|
285
|
+
return
|
|
286
|
+
|
|
287
|
+
# ── unlink confirm/cancel ──────────────────────────────────────────────
|
|
288
|
+
if data == "unlink:confirm":
|
|
289
|
+
async with httpx.AsyncClient(timeout=10) as c:
|
|
290
|
+
await c.delete(
|
|
291
|
+
f"{API_URL}/api/v1/users/telegram-unlink",
|
|
292
|
+
headers={"Authorization": f"X-User-Id {user['id']}", **_bot_headers(user["id"])},
|
|
293
|
+
)
|
|
294
|
+
await query.edit_message_text("✅ Verknüpfung aufgehoben. Auf Wiedersehen!")
|
|
295
|
+
return
|
|
296
|
+
if data == "unlink:cancel":
|
|
297
|
+
await query.edit_message_text("❌ Abgebrochen. Verknüpfung bleibt bestehen.")
|
|
298
|
+
return
|
|
299
|
+
|
|
300
|
+
# ── show trip timeline ─────────────────────────────────────────────────
|
|
301
|
+
if data.startswith("trip:"):
|
|
302
|
+
parts = data.split(":", 2)
|
|
303
|
+
trip_id = parts[1]
|
|
304
|
+
trip_name = parts[2] if len(parts) > 2 else "Trip"
|
|
305
|
+
async with httpx.AsyncClient(timeout=10) as c:
|
|
306
|
+
r = await c.get(f"{API_URL}/api/v1/trips/{trip_id}/items", headers=_bot_headers(user["id"]))
|
|
307
|
+
if r.status_code != 200 or not r.json():
|
|
308
|
+
await query.edit_message_text(f"📂 *{trip_name}* hat noch keine Einträge.", parse_mode="Markdown")
|
|
309
|
+
return
|
|
310
|
+
items = r.json()
|
|
311
|
+
lines = [f"📅 *{trip_name}* — {len(items)} Einträge\n"]
|
|
312
|
+
for i in items[:10]:
|
|
313
|
+
icon = _ICONS.get(i.get("type", "other"), "📋")
|
|
314
|
+
time = i.get("event_at", "")[:16].replace("T", " ") if i.get("event_at") else ""
|
|
315
|
+
lines.append(f"{icon} *{i['title']}*{(' · ' + time) if time else ''}")
|
|
316
|
+
if len(items) > 10:
|
|
317
|
+
lines.append(f"… +{len(items) - 10} weitere")
|
|
318
|
+
await query.edit_message_text("\n".join(lines), parse_mode="Markdown")
|
|
319
|
+
return
|
|
320
|
+
|
|
321
|
+
# ── assign inbox item — pick trip ──────────────────────────────────────
|
|
322
|
+
if data.startswith("assign:") and data.endswith(":pick"):
|
|
323
|
+
inbox_id = data.split(":")[1]
|
|
324
|
+
async with httpx.AsyncClient(timeout=10) as c:
|
|
325
|
+
r = await c.get(f"{API_URL}/api/v1/trips", headers=_bot_headers(user["id"]))
|
|
326
|
+
trips = r.json() if r.status_code == 200 else []
|
|
327
|
+
if not trips:
|
|
328
|
+
await query.edit_message_text("📭 Keine Trips vorhanden. Erstelle zuerst einen Trip in der App.")
|
|
329
|
+
return
|
|
330
|
+
buttons = [
|
|
331
|
+
[InlineKeyboardButton(t["name"], callback_data=f"assign:{inbox_id}:{t['id']}")]
|
|
332
|
+
for t in trips[:8]
|
|
333
|
+
]
|
|
334
|
+
buttons.append([InlineKeyboardButton("❌ Abbrechen", callback_data=f"assign:{inbox_id}:cancel")])
|
|
335
|
+
await query.edit_message_reply_markup(InlineKeyboardMarkup(buttons))
|
|
336
|
+
return
|
|
337
|
+
|
|
338
|
+
if data.startswith("assign:") and data.endswith(":cancel"):
|
|
339
|
+
await query.edit_message_reply_markup(None)
|
|
340
|
+
return
|
|
341
|
+
|
|
342
|
+
# ── assign inbox item to specific trip ─────────────────────────────────
|
|
343
|
+
if data.startswith("assign:"):
|
|
344
|
+
parts = data.split(":", 2)
|
|
345
|
+
inbox_id, trip_id = parts[1], parts[2]
|
|
346
|
+
if trip_id in ("pick", "cancel"):
|
|
347
|
+
return
|
|
348
|
+
async with httpx.AsyncClient(timeout=10) as c:
|
|
349
|
+
r = await c.post(
|
|
350
|
+
f"{API_URL}/api/v1/inbox/{inbox_id}/assign",
|
|
351
|
+
json={"trip_id": trip_id, "type": "other"},
|
|
352
|
+
headers=_bot_headers(user["id"]),
|
|
353
|
+
)
|
|
354
|
+
if r.status_code == 200:
|
|
355
|
+
await query.edit_message_text("✅ Dem Trip zugewiesen!", parse_mode="Markdown")
|
|
356
|
+
else:
|
|
357
|
+
await query.edit_message_text(f"⚠️ Fehler beim Zuweisen ({r.status_code}).")
|
|
358
|
+
return
|
|
359
|
+
|
|
360
|
+
# ── reject inbox item ──────────────────────────────────────────────────
|
|
361
|
+
if data.startswith("reject:"):
|
|
362
|
+
inbox_id = data.split(":")[1]
|
|
363
|
+
async with httpx.AsyncClient(timeout=10) as c:
|
|
364
|
+
await c.delete(f"{API_URL}/api/v1/inbox/{inbox_id}", headers=_bot_headers(user["id"]))
|
|
365
|
+
await query.edit_message_text("🗑️ Gelöscht.")
|
|
366
|
+
return
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
# ── Message handlers ────────────────────────────────────────────────────────────
|
|
370
|
+
|
|
371
|
+
async def handle_document(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
|
372
|
+
user = await _require_link(update)
|
|
373
|
+
if not user:
|
|
374
|
+
return
|
|
375
|
+
msg = await update.message.reply_text("⏳ Analysiere Dokument…")
|
|
376
|
+
doc = update.message.document
|
|
377
|
+
photo = update.message.photo
|
|
378
|
+
try:
|
|
379
|
+
if photo:
|
|
380
|
+
file = await ctx.bot.get_file(photo[-1].file_id)
|
|
381
|
+
mime, fname = "image/jpeg", "photo.jpg"
|
|
382
|
+
elif doc:
|
|
383
|
+
file = await ctx.bot.get_file(doc.file_id)
|
|
384
|
+
mime = doc.mime_type or "application/octet-stream"
|
|
385
|
+
fname = doc.file_name or "document"
|
|
386
|
+
else:
|
|
387
|
+
await msg.edit_text("⚠️ Kein Dokument erkannt.")
|
|
388
|
+
return
|
|
389
|
+
file_bytes = await file.download_as_bytearray()
|
|
390
|
+
async with httpx.AsyncClient(timeout=60) as c:
|
|
391
|
+
r = await c.post(
|
|
392
|
+
f"{API_URL}/api/v1/parse/file",
|
|
393
|
+
files={"file": (fname, bytes(file_bytes), mime)},
|
|
394
|
+
headers=_bot_headers(user["id"]),
|
|
395
|
+
)
|
|
396
|
+
if r.status_code == 200:
|
|
397
|
+
parsed = r.json().get("parsed", {})
|
|
398
|
+
title = parsed.get("title") or "Dokument"
|
|
399
|
+
itype = parsed.get("type") or "other"
|
|
400
|
+
icon = _ICONS.get(itype, "📋")
|
|
401
|
+
ref = parsed.get("booking_ref") or ""
|
|
402
|
+
provider = parsed.get("provider") or ""
|
|
403
|
+
event_at = parsed.get("event_at") or ""
|
|
404
|
+
lines = [f"{icon} *{title}*", f"Typ: {itype}"]
|
|
405
|
+
if provider:
|
|
406
|
+
lines.append(f"Anbieter: {provider}")
|
|
407
|
+
if ref:
|
|
408
|
+
lines.append(f"Ref: `{ref}`")
|
|
409
|
+
if event_at:
|
|
410
|
+
lines.append(f"Datum: {event_at[:16].replace('T', ' ')}")
|
|
411
|
+
lines.append("\n📥 Im Chaos Inbox")
|
|
412
|
+
# Fetch trips for inline assignment
|
|
413
|
+
async with httpx.AsyncClient(timeout=10) as c2:
|
|
414
|
+
trips_r = await c2.get(f"{API_URL}/api/v1/trips", headers=_bot_headers(user["id"]))
|
|
415
|
+
inbox_id = r.json().get("inbox_id")
|
|
416
|
+
keyboard = None
|
|
417
|
+
if inbox_id and trips_r.status_code == 200 and trips_r.json():
|
|
418
|
+
trips = trips_r.json()[:6]
|
|
419
|
+
buttons = [
|
|
420
|
+
[InlineKeyboardButton(f"➡️ {t['name']}", callback_data=f"assign:{inbox_id}:{t['id']}")]
|
|
421
|
+
for t in trips
|
|
422
|
+
]
|
|
423
|
+
buttons.append([InlineKeyboardButton("🗑️ Ablehnen", callback_data=f"reject:{inbox_id}")])
|
|
424
|
+
keyboard = InlineKeyboardMarkup(buttons)
|
|
425
|
+
await msg.edit_text(
|
|
426
|
+
"\n".join(lines),
|
|
427
|
+
reply_markup=keyboard,
|
|
428
|
+
parse_mode="Markdown",
|
|
429
|
+
)
|
|
430
|
+
elif r.status_code == 429:
|
|
431
|
+
await msg.edit_text("⚠️ Tageslimit erreicht. Morgen wieder verfügbar (oder auf Pro upgraden).")
|
|
432
|
+
else:
|
|
433
|
+
await msg.edit_text(f"⚠️ Fehler beim Parsen ({r.status_code}). Prüfe Ollama.")
|
|
434
|
+
except Exception:
|
|
435
|
+
log.exception("handle_document error")
|
|
436
|
+
await msg.edit_text("⚠️ Technischer Fehler. Bitte versuche es erneut.")
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
async def handle_text(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
|
440
|
+
text = update.message.text or ""
|
|
441
|
+
if text.startswith("/"):
|
|
442
|
+
return
|
|
443
|
+
user = await _require_link(update)
|
|
444
|
+
if not user:
|
|
445
|
+
return
|
|
446
|
+
if len(text) < 20:
|
|
447
|
+
await update.message.reply_text("Text zu kurz zum Parsen. Bitte Buchungsbestätigung oder E-Mail einfügen.")
|
|
448
|
+
return
|
|
449
|
+
msg = await update.message.reply_text("⏳ Analysiere Text…")
|
|
450
|
+
async with httpx.AsyncClient(timeout=60) as c:
|
|
451
|
+
r = await c.post(
|
|
452
|
+
f"{API_URL}/api/v1/parse/text",
|
|
453
|
+
data={"raw_text": text},
|
|
454
|
+
headers=_bot_headers(user["id"]),
|
|
455
|
+
)
|
|
456
|
+
if r.status_code == 200:
|
|
457
|
+
parsed = r.json().get("parsed", {})
|
|
458
|
+
title = parsed.get("title") or "Text"
|
|
459
|
+
itype = parsed.get("type") or "other"
|
|
460
|
+
icon = _ICONS.get(itype, "📋")
|
|
461
|
+
inbox_id = r.json().get("inbox_id")
|
|
462
|
+
# Build inline keyboard for assignment
|
|
463
|
+
keyboard = None
|
|
464
|
+
if inbox_id:
|
|
465
|
+
async with httpx.AsyncClient(timeout=10) as c2:
|
|
466
|
+
trips_r = await c2.get(f"{API_URL}/api/v1/trips", headers=_bot_headers(user["id"]))
|
|
467
|
+
if trips_r.status_code == 200 and trips_r.json():
|
|
468
|
+
trips = trips_r.json()[:6]
|
|
469
|
+
buttons = [
|
|
470
|
+
[InlineKeyboardButton(f"➡️ {t['name']}", callback_data=f"assign:{inbox_id}:{t['id']}")]
|
|
471
|
+
for t in trips
|
|
472
|
+
]
|
|
473
|
+
buttons.append([InlineKeyboardButton("🗑️ Ablehnen", callback_data=f"reject:{inbox_id}")])
|
|
474
|
+
keyboard = InlineKeyboardMarkup(buttons)
|
|
475
|
+
await msg.edit_text(
|
|
476
|
+
f"{icon} *{title}* erkannt und im Inbox gespeichert.",
|
|
477
|
+
reply_markup=keyboard,
|
|
478
|
+
parse_mode="Markdown",
|
|
479
|
+
)
|
|
480
|
+
elif r.status_code == 429:
|
|
481
|
+
await msg.edit_text("⚠️ Tageslimit erreicht.")
|
|
482
|
+
else:
|
|
483
|
+
await msg.edit_text("⚠️ Konnte Text nicht parsen.")
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def main():
|
|
487
|
+
app = Application.builder().token(TOKEN).build()
|
|
488
|
+
app.add_handler(CommandHandler("start", cmd_start))
|
|
489
|
+
app.add_handler(CommandHandler("help", cmd_help))
|
|
490
|
+
app.add_handler(CommandHandler("link", cmd_link))
|
|
491
|
+
app.add_handler(CommandHandler("unlink", cmd_unlink))
|
|
492
|
+
app.add_handler(CommandHandler("status", cmd_status))
|
|
493
|
+
app.add_handler(CommandHandler("trips", cmd_trips))
|
|
494
|
+
app.add_handler(CommandHandler("next", cmd_next))
|
|
495
|
+
app.add_handler(CommandHandler("inbox", cmd_inbox))
|
|
496
|
+
app.add_handler(CallbackQueryHandler(handle_callback))
|
|
497
|
+
app.add_handler(MessageHandler(filters.Document.ALL | filters.PHOTO, handle_document))
|
|
498
|
+
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_text))
|
|
499
|
+
log.info("TCO Bot starting…")
|
|
500
|
+
app.run_polling(drop_pending_updates=True)
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
if __name__ == "__main__":
|
|
504
|
+
main()
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* telegram-notify — shared Telegram notification client.
|
|
3
|
+
*
|
|
4
|
+
* Used by cachly-mcp (TypeScript) and TCO backend (Python via client.py).
|
|
5
|
+
* Single source of truth for: emoji map, message format, env var convention.
|
|
6
|
+
*
|
|
7
|
+
* Interface: notify(app, event, payload)
|
|
8
|
+
*
|
|
9
|
+
* Env vars:
|
|
10
|
+
* TELEGRAM_BOT_TOKEN — bot token from @BotFather
|
|
11
|
+
* {APP_UPPER}_TELEGRAM_CHAT_ID — app-specific channel (e.g. CACHLY_TELEGRAM_CHAT_ID)
|
|
12
|
+
* TELEGRAM_CHAT_ID — fallback for all apps
|
|
13
|
+
*
|
|
14
|
+
* No-op when unconfigured. Never throws.
|
|
15
|
+
*
|
|
16
|
+
* Note: emoji map is kept in sync with emojis.json (Python reads the JSON file at runtime).
|
|
17
|
+
*/
|
|
18
|
+
export declare function formatMessage(app: string, event: string, payload: Record<string, unknown>): string;
|
|
19
|
+
export declare function notify(app: string, event: string, payload?: Record<string, unknown>): Promise<void>;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* telegram-notify — shared Telegram notification client.
|
|
3
|
+
*
|
|
4
|
+
* Used by cachly-mcp (TypeScript) and TCO backend (Python via client.py).
|
|
5
|
+
* Single source of truth for: emoji map, message format, env var convention.
|
|
6
|
+
*
|
|
7
|
+
* Interface: notify(app, event, payload)
|
|
8
|
+
*
|
|
9
|
+
* Env vars:
|
|
10
|
+
* TELEGRAM_BOT_TOKEN — bot token from @BotFather
|
|
11
|
+
* {APP_UPPER}_TELEGRAM_CHAT_ID — app-specific channel (e.g. CACHLY_TELEGRAM_CHAT_ID)
|
|
12
|
+
* TELEGRAM_CHAT_ID — fallback for all apps
|
|
13
|
+
*
|
|
14
|
+
* No-op when unconfigured. Never throws.
|
|
15
|
+
*
|
|
16
|
+
* Note: emoji map is kept in sync with emojis.json (Python reads the JSON file at runtime).
|
|
17
|
+
*/
|
|
18
|
+
const EMOJI = {
|
|
19
|
+
tool_call: '🔧',
|
|
20
|
+
tool_error: '🔴',
|
|
21
|
+
cache_hit: '⚡',
|
|
22
|
+
cache_miss: '💭',
|
|
23
|
+
auth_ok: '🔑',
|
|
24
|
+
auth_fail: '🚫',
|
|
25
|
+
startup: '🚀',
|
|
26
|
+
waitlist_signup: '📬',
|
|
27
|
+
parse_file: '📄',
|
|
28
|
+
parse_text: '📝',
|
|
29
|
+
parse_url: '🔗',
|
|
30
|
+
trip_created: '🗺️',
|
|
31
|
+
trip_deleted: '🗑️',
|
|
32
|
+
error: '🔴',
|
|
33
|
+
stripe_payment: '💳',
|
|
34
|
+
stripe_checkout: '🛒',
|
|
35
|
+
new_user: '🆕',
|
|
36
|
+
pdf_export: '📑',
|
|
37
|
+
trip_search: '🔍',
|
|
38
|
+
default: '🔔',
|
|
39
|
+
};
|
|
40
|
+
function getToken() {
|
|
41
|
+
return process.env.TELEGRAM_BOT_TOKEN ?? '';
|
|
42
|
+
}
|
|
43
|
+
function getChatId(app) {
|
|
44
|
+
const key = `${app.toUpperCase()}_TELEGRAM_CHAT_ID`;
|
|
45
|
+
return process.env[key] ?? process.env.TELEGRAM_CHAT_ID ?? '';
|
|
46
|
+
}
|
|
47
|
+
export function formatMessage(app, event, payload) {
|
|
48
|
+
const icon = EMOJI[event] ?? EMOJI['default'];
|
|
49
|
+
const lines = [`${icon} *${app.toUpperCase()}* · \`${event}\``];
|
|
50
|
+
for (const [k, v] of Object.entries(payload)) {
|
|
51
|
+
if (v !== null && v !== undefined && v !== '') {
|
|
52
|
+
lines.push(` ${k}: \`${String(v)}\``);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return lines.join('\n');
|
|
56
|
+
}
|
|
57
|
+
export async function notify(app, event, payload = {}) {
|
|
58
|
+
const token = getToken();
|
|
59
|
+
const chatId = getChatId(app);
|
|
60
|
+
if (!token || !chatId)
|
|
61
|
+
return;
|
|
62
|
+
try {
|
|
63
|
+
const text = formatMessage(app, event, payload);
|
|
64
|
+
await fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
|
|
65
|
+
method: 'POST',
|
|
66
|
+
headers: { 'Content-Type': 'application/json' },
|
|
67
|
+
body: JSON.stringify({ chat_id: chatId, text, parse_mode: 'Markdown' }),
|
|
68
|
+
signal: AbortSignal.timeout(5000),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// never throws
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../../packages/telegram-notify/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH,MAAM,KAAK,GAA2B;IACpC,SAAS,EAAW,IAAI;IACxB,UAAU,EAAU,IAAI;IACxB,SAAS,EAAW,GAAG;IACvB,UAAU,EAAU,IAAI;IACxB,OAAO,EAAa,IAAI;IACxB,SAAS,EAAW,IAAI;IACxB,OAAO,EAAa,IAAI;IACxB,eAAe,EAAK,IAAI;IACxB,UAAU,EAAU,IAAI;IACxB,UAAU,EAAU,IAAI;IACxB,SAAS,EAAW,IAAI;IACxB,YAAY,EAAQ,KAAK;IACzB,YAAY,EAAQ,KAAK;IACzB,KAAK,EAAe,IAAI;IACxB,cAAc,EAAM,IAAI;IACxB,eAAe,EAAK,IAAI;IACxB,QAAQ,EAAY,IAAI;IACxB,UAAU,EAAU,IAAI;IACxB,WAAW,EAAS,IAAI;IACxB,OAAO,EAAa,IAAI;CACzB,CAAC;AAEF,SAAS,QAAQ;IACf,OAAO,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,EAAE,CAAC;AAC9C,CAAC;AAED,SAAS,SAAS,CAAC,GAAW;IAC5B,MAAM,GAAG,GAAG,GAAG,GAAG,CAAC,WAAW,EAAE,mBAAmB,CAAC;IACpD,OAAO,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,EAAE,CAAC;AAChE,CAAC;AAED,MAAM,UAAU,aAAa,CAC3B,GAAW,EACX,KAAa,EACb,OAAgC;IAEhC,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,SAAS,CAAC,CAAC;IAC9C,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,KAAK,GAAG,CAAC,WAAW,EAAE,SAAS,KAAK,IAAI,CAAC,CAAC;IAChE,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC7C,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC;YAC9C,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACzC,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,MAAM,CAC1B,GAAW,EACX,KAAa,EACb,UAAmC,EAAE;IAErC,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAC;IACzB,MAAM,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;IAC9B,IAAI,CAAC,KAAK,IAAI,CAAC,MAAM;QAAE,OAAO;IAC9B,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,aAAa,CAAC,GAAG,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;QAChD,MAAM,KAAK,CAAC,+BAA+B,KAAK,cAAc,EAAE;YAC9D,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC;YACvE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC;SAClC,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,eAAe;IACjB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.test.js","sourceRoot":"","sources":["../../../src/__tests__/auth.test.ts"],"names":[],"mappings":"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"}
|