feat(sources): форма добавления источника — одно поле Address, авто-название из TG/RSS (address-field-enhancement)

- parse_address(address, crawler) в sources/sources.py: telegram (t.me/name, @name,
  https://t.me/s/name) → slug/channel/url; rss → feed_url/url/slug из домена.
- GET /sources/preview: telegram — название через Telethon get_entity (title),
  rss — через feedparser (feed.title); slug и name возвращаются в JSON.
- _form_source: единое поле address вместо url/channel/feed_url; slug из адреса.
- /sources: direction datalist из DIRECTIONS + уникальные значения из БД,
  свободный ввод остаётся.
- Форма: поле Address + кнопка «Найти» (JS fetch → заполнение slug/name),
  direction datalist динамический.

Также в sources.py входит незакоммиченная ранее веб-CRUD-логика (upsert_source_yaml,
delete_source_yaml, sync_yaml_to_db_and_back и др.) — работала на проде, теперь в git.
This commit is contained in:
kpa39l
2026-09-16 17:03:23 +00:00
parent 87134ed839
commit 48c25f13d4
5 changed files with 329 additions and 48 deletions
+30 -17
View File
@@ -23,13 +23,13 @@
<input type="text" name="name" class="form-control form-control-sm" placeholder="My Channel">
</div>
<div class="col-auto">
<label class="form-label mb-0 small">URL</label>
<input type="text" name="url" class="form-control form-control-sm" placeholder="https://t.me/... или RSS">
<label class="form-label mb-0 small">Address</label>
<input type="text" name="address" class="form-control form-control-sm"
placeholder="@handle, t.me/name, https://t.me/name или https://.../rss" id="add_address">
</div>
<div class="col-auto">
<label class="form-label mb-0 small">channel (telegram)</label>
<input type="text" name="channel" class="form-control form-control-sm"
placeholder="@handle или t.me/..." id="add_channel">
<button type="button" class="btn btn-sm btn-outline-secondary" id="btn_fetch"
onclick="fetchSourcePreview()">🔎 Найти</button>
</div>
<div class="col-auto">
<label class="form-label mb-0 small">crawler</label>
@@ -38,16 +38,11 @@
<option value="rss">rss</option>
</select>
</div>
<div class="col-auto">
<label class="form-label mb-0 small">feed_url (rss)</label>
<input type="text" name="feed_url" class="form-control form-control-sm" placeholder="https://.../rss">
</div>
<div class="col-auto">
<label class="form-label mb-0 small">direction</label>
<input type="text" name="direction" class="form-control form-control-sm" placeholder="linux" list="dirs">
<datalist id="dirs">
<option value="linux"><option value="tech"><option value="politics">
<option value="games"><option value="electronics"><option value="llm">
{% for d in directions %}<option value="{{ d }}">{% endfor %}
</datalist>
</div>
<div class="col-auto">
@@ -67,13 +62,31 @@
</form>
<script>
const addCrawler = document.querySelector('form[action="/sources/add"] select[name="crawler"]');
const addChannel = document.getElementById('add_channel');
if (addCrawler && addChannel) {
function toggleChannelRequired() {
addChannel.required = addCrawler.value === 'telegram';
const addAddress = document.getElementById('add_address');
const addSlug = document.querySelector('form[action="/sources/add"] input[name="slug"]');
const addName = document.querySelector('form[action="/sources/add"] input[name="name"]');
if (addCrawler && addAddress) {
function toggleAddress() {
addAddress.placeholder = addCrawler.value === 'telegram'
? '@handle, t.me/name, https://t.me/name'
: 'https://.../rss или http://...';
}
addCrawler.addEventListener('change', toggleChannelRequired);
toggleChannelRequired();
addCrawler.addEventListener('change', toggleAddress);
toggleAddress();
}
async function fetchSourcePreview() {
const addr = addAddress.value.trim();
if (!addr) { alert('Введите адрес'); return; }
try {
const resp = await fetch(`/sources/preview?address=${encodeURIComponent(addr)}&crawler=${addCrawler.value}`);
const data = await resp.json();
if (data.error) { alert(data.error); return; }
if (data.slug) addSlug.value = data.slug;
if (data.name) { addName.value = data.name; }
else if (addName.value === '') addName.placeholder = 'Название не найдено — введите вручную';
} catch (e) { alert('Ошибка запроса: ' + e); }
}
</script>
</div>