add testing feature

This commit is contained in:
2026-05-23 10:15:09 +10:00
parent 227bc9b1de
commit 466b9c1fcb
4 changed files with 120 additions and 5 deletions
+58 -1
View File
@@ -27,7 +27,8 @@
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">{% trans "Bot Token" %}</label>
<input
type="text"
type="password"
id="telegram_bot_token"
name="telegram_bot_token"
value="{{ site_settings.telegram_bot_token }}"
placeholder="123456:ABC-DEF1234..."
@@ -39,6 +40,7 @@
<label class="block text-sm font-medium text-gray-700 mb-1">{% trans "Chat ID" %}</label>
<input
type="text"
id="telegram_chat_id"
name="telegram_chat_id"
value="{{ site_settings.telegram_chat_id }}"
placeholder="-100123456789"
@@ -47,6 +49,17 @@
<p class="mt-1 text-xs text-gray-400">{% trans "User ID or group chat ID." %}</p>
</div>
</div>
<div class="mt-4 flex justify-end">
<button
type="button"
id="telegram-test-button"
class="px-4 py-2 bg-white text-blue-600 border border-blue-200 rounded-md text-sm font-medium hover:bg-blue-50"
>
{% trans "Send Test Message" %}
</button>
</div>
<p id="telegram-test-status" class="mt-2 text-xs text-gray-400"></p>
</div>
<!-- Public Sharing Domain -->
@@ -145,6 +158,8 @@
<div class="flex justify-end mt-6">
<button
type="submit"
name="save_settings"
value="1"
style="background:#1d4ed8;color:#fff;padding:.5rem 1.5rem;border-radius:.375rem;font-size:.875rem;font-weight:500;cursor:pointer;"
onmouseover="this.style.background='#1e40af'"
onmouseout="this.style.background='#1d4ed8'"
@@ -211,6 +226,48 @@
{% block extra_js %}
<script>
document.addEventListener('DOMContentLoaded', () => {
const button = document.getElementById('telegram-test-button');
const status = document.getElementById('telegram-test-status');
if (!button || !status) return;
button.addEventListener('click', async () => {
const csrf = document.querySelector('[name=csrfmiddlewaretoken]')?.value
|| document.cookie.match(/csrftoken=([^;]+)/)?.[1] || '';
const token = document.getElementById('telegram_bot_token')?.value || '';
const chatId = document.getElementById('telegram_chat_id')?.value || '';
button.disabled = true;
status.className = 'mt-2 text-xs text-gray-500';
status.textContent = '{% trans "Sending test message…" %}';
try {
const resp = await fetch('{% url "test-telegram" %}', {
method: 'POST',
headers: {
'X-CSRFToken': csrf,
'Content-Type': 'application/json',
},
body: JSON.stringify({ token, chat_id: chatId }),
});
const result = await resp.json();
if (result.ok) {
status.className = 'mt-2 text-xs text-green-600';
status.textContent = '{% trans "Test message sent." %}';
} else {
status.className = 'mt-2 text-xs text-red-600';
status.textContent = result.error || '{% trans "Telegram test failed." %}';
}
} catch (error) {
status.className = 'mt-2 text-xs text-red-600';
status.textContent = error.message || '{% trans "Telegram test failed." %}';
} finally {
button.disabled = false;
}
});
});
function sharedPostsMgr() {
return {
posts: [],
+1
View File
@@ -22,6 +22,7 @@ urlpatterns = [
path('tools/export-database/', views.export_database, name='export_database'),
path('ui/help/', views.HelpView.as_view(), name='help'),
path('ui/settings/', views.SiteSettingsView.as_view(), name='site-settings'),
path('ui/settings/test-telegram/', views.test_telegram_view, name='test-telegram'),
path('ui/jobs/', views.JobsView.as_view(), name='jobs'),
# Pages
+56 -1
View File
@@ -32,7 +32,7 @@ from wsgiref.util import FileWrapper
from django.contrib.auth.decorators import user_passes_test
from django.contrib.auth.mixins import LoginRequiredMixin
import requests
from django.views.decorators.http import require_http_methods
from django.views.decorators.http import require_http_methods, require_POST
from django.views.decorators.csrf import csrf_exempt
from bs4 import BeautifulSoup
@@ -561,6 +561,46 @@ class HelpView(TemplateView):
template_name = 'links/help.html'
@require_POST
@csrf_exempt # CSRF is checked manually via header
def test_telegram_view(request):
"""Dedicated endpoint for testing Telegram credentials without saving them."""
import json
import requests as req
# Accept both JSON body and form-encoded body
if request.content_type and 'application/json' in request.content_type:
try:
body = json.loads(request.body)
except (ValueError, KeyError):
body = {}
token = body.get('token', '').strip()
chat_id = body.get('chat_id', '').strip()
else:
token = request.POST.get('token', '').strip()
chat_id = request.POST.get('chat_id', '').strip()
if not token or not chat_id:
return JsonResponse({'ok': False, 'error': _('Bot Token and Chat ID are required.')})
try:
resp = req.post(
f'https://api.telegram.org/bot{token}/sendMessage',
json={'chat_id': chat_id, 'text': '✅ Test notification from Links app — Telegram is working!'},
timeout=10,
)
resp.raise_for_status()
return JsonResponse({'ok': True})
except req.exceptions.HTTPError as exc:
try:
detail = exc.response.json().get('description', str(exc))
except Exception:
detail = str(exc)
return JsonResponse({'ok': False, 'error': detail})
except Exception as exc:
return JsonResponse({'ok': False, 'error': str(exc)})
class SiteSettingsView(View):
template_name = 'links/settings.html'
@@ -575,8 +615,23 @@ class SiteSettingsView(View):
def post(self, request):
from apscheduler.triggers.interval import IntervalTrigger
from core.scheduler import scheduler
from pricemon.notifications import send_test_telegram
site_settings = SiteSettings.get()
if 'test_telegram' in request.POST:
token = request.POST.get('telegram_bot_token', '').strip()
chat_id = request.POST.get('telegram_chat_id', '').strip()
result = send_test_telegram(token, chat_id)
if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
status_code = 200 if result['ok'] else 400
return JsonResponse(result, status=status_code)
if result['ok']:
messages.success(request, _('Test Telegram message sent.'))
else:
messages.error(request, _('Telegram test failed: {}').format(result['error']))
return redirect('site-settings')
site_settings.telegram_bot_token = request.POST.get('telegram_bot_token', '').strip()
site_settings.telegram_chat_id = request.POST.get('telegram_chat_id', '').strip()
site_settings.public_sharing_domain = request.POST.get('public_sharing_domain', '').strip()
+5 -3
View File
@@ -5,7 +5,9 @@ import requests
logger = logging.getLogger(__name__)
def _get_telegram_credentials():
def _get_telegram_credentials(token: str | None = None, chat_id: str | None = None):
if token is not None and chat_id is not None:
return token, chat_id
from links.models import SiteSettings
cfg = SiteSettings.get()
return cfg.telegram_bot_token, cfg.telegram_chat_id
@@ -35,8 +37,8 @@ def notify_telegram(watcher, alert) -> None:
logger.error(f'Failed to send Telegram alert for watcher {watcher.pk}: {exc}')
def send_test_telegram() -> dict:
token, chat_id = _get_telegram_credentials()
def send_test_telegram(token: str | None = None, chat_id: str | None = None) -> dict:
token, chat_id = _get_telegram_credentials(token, chat_id)
if not token or not chat_id:
return {'ok': False, 'error': 'No Telegram credentials in global Settings.'}
try: