Added telegram bot
Build and Publish / Build and Publish Docker Image (push) Successful in 3m8s

This commit is contained in:
Ultradesu
2026-06-30 18:36:53 +03:00
parent 3def3afeda
commit 3a4bc23a58
12 changed files with 915 additions and 19 deletions
+163
View File
@@ -3,6 +3,7 @@
{% block title %}{{ t.configs_portal_heading }} | {{ t.site_name }}{% endblock title %}
{% block head_extra %}
<script src="https://telegram.org/js/telegram-web-app.js"></script>
<script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
<style>
* { box-sizing: border-box; }
@@ -105,6 +106,11 @@
.qr-box { display: grid; place-items: center; padding: .75rem; border: 1px solid #d6dfda; border-radius: 8px; background: #fff; }
.qr-box svg { width: min(280px, 100%); height: auto; display: block; }
.modal-actions { display: flex; justify-content: flex-end; gap: .5rem; flex-wrap: wrap; }
.telegram-sheet { width: min(480px, 100%); }
.telegram-copy { color: #53616c; line-height: 1.45; margin: 0; }
.guide-list { margin: 0; padding-left: 1.2rem; color: #34424b; display: grid; gap: .45rem; line-height: 1.4; }
.guide-list a { color: #18342f; font-weight: 850; }
.guide-list a.disabled { color: #69777f; pointer-events: none; text-decoration: none; }
@media (max-width: 720px) {
.topbar-inner { align-items: flex-start; flex-direction: column; }
.top-actions { width: 100%; justify-content: space-between; }
@@ -324,6 +330,56 @@
</section>
</div>
</template>
<template x-if="telegramModal">
<div class="backdrop" x-cloak>
<section class="sheet telegram-sheet">
<div class="sheet-head">
<div class="sheet-title">
<h2>{{ t.telegram_link_title }}</h2>
<span x-text="telegramBotLabel()"></span>
</div>
</div>
<template x-if="telegramModal === 'webapp'">
<div>
<p class="telegram-copy">{{ t.telegram_webapp_message }}</p>
<div class="modal-actions">
<button type="button" class="secondary" @click="declineTelegramLink()" :disabled="telegramBusy">{{ t.telegram_skip }}</button>
<button type="button" @click="linkTelegramWebApp()" :disabled="telegramBusy">{{ t.telegram_save }}</button>
</div>
</div>
</template>
<template x-if="telegramModal === 'manualPrompt'">
<div>
<p class="telegram-copy">{{ t.telegram_manual_message }}</p>
<div class="modal-actions">
<button type="button" class="secondary" @click="declineTelegramLink()" :disabled="telegramBusy">{{ t.telegram_skip }}</button>
<button type="button" @click="telegramModal = 'manualGuide'">{{ t.telegram_connect }}</button>
</div>
</div>
</template>
<template x-if="telegramModal === 'manualGuide'">
<div class="field">
<p class="eyebrow">{{ t.telegram_guide_title }}</p>
<ol class="guide-list">
<li>{{ t.telegram_guide_start }} <a :href="telegramBotUrl()" target="_blank" rel="noreferrer" :class="{ disabled: !telegram.bot_url }">{{ t.telegram_open_bot }}</a></li>
<li>{{ t.telegram_guide_get_id }}</li>
<li>{{ t.telegram_guide_paste }}</li>
</ol>
<label for="telegram-id">{{ t.telegram_id_label }}</label>
<input id="telegram-id" x-model="telegramManualId" inputmode="numeric" pattern="[0-9]*" placeholder="{{ t.telegram_id_placeholder }}" @keydown.enter.prevent="saveTelegramManual()">
<div class="modal-actions">
<button type="button" class="secondary" @click="declineTelegramLink()" :disabled="telegramBusy">{{ t.telegram_skip }}</button>
<button type="button" @click="saveTelegramManual()" :disabled="telegramBusy || !telegramManualId.trim()">{{ t.telegram_save }}</button>
</div>
</div>
</template>
</section>
</div>
</template>
</div>
<script>
@@ -342,9 +398,25 @@ function clientPortal() {
serverConfigs: {},
serverModal: null,
qrModal: null,
telegram: {
enabled: false,
bot_username: '',
bot_url: '',
linked: false,
declined: false,
telegram_id: null,
isWebApp: false,
initData: '',
},
telegramModal: null,
telegramManualId: '',
telegramBusy: false,
telegramPromptChecked: false,
init() {
this.initTelegramWebApp();
this.load();
this.loadServerStatus();
this.loadTelegramStatus();
this.serverStatusTimer = setInterval(() => this.loadServerStatus(true), 30000);
},
async request(url, options = {}) {
@@ -386,6 +458,97 @@ function clientPortal() {
this.serverStatusBusy = false;
}
},
initTelegramWebApp() {
const webApp = window.Telegram && window.Telegram.WebApp;
if (!webApp) return;
try {
webApp.ready();
webApp.expand();
} catch (_) {}
this.telegram.isWebApp = Boolean(webApp.initData);
this.telegram.initData = webApp.initData || '';
},
async loadTelegramStatus() {
try {
const status = await this.request('/api/telegram-link/status');
this.applyTelegramStatus(status);
this.maybePromptTelegram();
} catch (e) {
console.warn('telegram status failed', e);
}
},
applyTelegramStatus(status) {
this.telegram = {
...this.telegram,
enabled: Boolean(status.enabled),
bot_username: status.bot_username || '',
bot_url: status.bot_url || '',
linked: Boolean(status.linked),
declined: Boolean(status.declined),
telegram_id: status.telegram_id || null,
};
},
maybePromptTelegram() {
if (this.telegramPromptChecked) return;
this.telegramPromptChecked = true;
if (!this.telegram.enabled || this.telegram.linked || this.telegram.declined) return;
this.telegramModal = (this.telegram.isWebApp && this.telegram.initData) ? 'webapp' : 'manualPrompt';
},
async linkTelegramWebApp() {
this.telegramBusy = true;
this.clearNotice();
try {
const data = await this.request('/api/telegram-link/webapp', {
method: 'POST',
body: JSON.stringify({ init_data: this.telegram.initData }),
});
this.applyTelegramStatus(data.status || {});
this.telegramModal = null;
this.setNotice('success', '{{ t.notice_success_title }}', '{{ t.telegram_saved }}');
} catch (e) {
this.showError(e);
} finally {
this.telegramBusy = false;
}
},
async saveTelegramManual() {
this.telegramBusy = true;
this.clearNotice();
try {
const data = await this.request('/api/telegram-link/manual', {
method: 'POST',
body: JSON.stringify({ telegram_id: this.telegramManualId }),
});
this.applyTelegramStatus(data.status || {});
this.telegramManualId = '';
this.telegramModal = null;
this.setNotice('success', '{{ t.notice_success_title }}', '{{ t.telegram_saved }}');
} catch (e) {
this.showError(e);
} finally {
this.telegramBusy = false;
}
},
async declineTelegramLink() {
this.telegramBusy = true;
this.clearNotice();
try {
const data = await this.request('/api/telegram-link/decline', { method: 'POST' });
this.applyTelegramStatus(data.status || {});
this.telegramModal = null;
this.setNotice('success', '{{ t.notice_success_title }}', '{{ t.telegram_declined }}');
} catch (e) {
this.showError(e);
} finally {
this.telegramBusy = false;
}
},
telegramBotUrl() {
return this.telegram.bot_url || '#';
},
telegramBotLabel() {
return this.telegram.bot_username ? `@${this.telegram.bot_username}` : '{{ t.telegram_bot_unconfigured }}';
},
async createClient() {
this.clearNotice();
this.busy = true;