Split RSS bots into their own category

New RSS_TYPES category with a /rss-bots page, sidebar entry, homepage tile, and an
explanation that RSS bots post a feed to a channel — share the channel link, not
the user. Remove rss from the Bots page (types table + create dropdown); the RSS
Bots page has a '+ New RSS Bot' button that only creates rss bots, with feed URL +
per hour/day/week fields shown directly (no bot-type select).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Jon
2026-06-05 23:43:45 +01:00
parent 87029f6d2a
commit 895cc6ddfa
5 changed files with 53 additions and 29 deletions

View File

@@ -71,9 +71,11 @@ def _enrich(profiles: list[dict]) -> list[dict]:
def _category(bot_type: str) -> str: def _category(bot_type: str) -> str:
"""Which sidebar category a profile belongs to: users / businesses / bots.""" """Which sidebar category a profile belongs to: users / businesses / rss-bots / bots."""
if bot_type in pm.BUSINESS_TYPES: if bot_type in pm.BUSINESS_TYPES:
return "businesses" return "businesses"
if bot_type in pm.RSS_TYPES:
return "rss-bots"
if bot_type in pm.USER_TYPES: if bot_type in pm.USER_TYPES:
return "users" return "users"
return "bots" return "bots"
@@ -144,6 +146,17 @@ async def bots_page(request: Request):
}) })
@app.get("/rss-bots", response_class=HTMLResponse)
async def rss_bots_page(request: Request):
if redir := _redirect_if_unauth(request):
return redir
items = _enrich([p for p in db.list_profiles() if p["bot_type"] in pm.RSS_TYPES])
return TEMPLATES.TemplateResponse(request, "list.html", {
"tab": "rss-bots", "items": items, "create_types": pm.RSS_TYPES,
"nav_active": "rss-bots",
})
RELAY_KINDS = {"chat": "Chat Relay", "file": "File Relay", "message": "Message Relay"} RELAY_KINDS = {"chat": "Chat Relay", "file": "File Relay", "message": "Message Relay"}

View File

@@ -139,10 +139,11 @@ def group_member_count(g: dict) -> int:
return g.get("groupSummary", {}).get("currentMembers", 0) return g.get("groupSummary", {}).get("currentMembers", 0)
BOT_TYPES = ["echo", "llm", "rss", "crypto", "broadcast", "support", "directory", "deadmans"] BOT_TYPES = ["echo", "llm", "crypto", "broadcast", "support", "directory", "deadmans"]
USER_TYPES = ["user"] USER_TYPES = ["user"]
BUSINESS_TYPES = ["business"] # cli accounts with a business address (per-customer group chats) BUSINESS_TYPES = ["business"] # cli accounts with a business address (per-customer group chats)
ALL_TYPES = BOT_TYPES + USER_TYPES + BUSINESS_TYPES RSS_TYPES = ["rss"] # feed bots that post to a channel (their own category)
ALL_TYPES = BOT_TYPES + USER_TYPES + BUSINESS_TYPES + RSS_TYPES
@dataclass @dataclass

View File

@@ -311,6 +311,7 @@
<!-- Group 1: accounts --> <!-- Group 1: accounts -->
<a href="/users" {% if nav_active == 'users' %}class="active"{% endif %}><span class="ico"><i class="fa-solid fa-user"></i></span><span class="lbl">Users</span></a> <a href="/users" {% if nav_active == 'users' %}class="active"{% endif %}><span class="ico"><i class="fa-solid fa-user"></i></span><span class="lbl">Users</span></a>
<a href="/bots" {% if nav_active == 'bots' %}class="active"{% endif %}><span class="ico"><i class="fa-solid fa-robot"></i></span><span class="lbl">Bots</span></a> <a href="/bots" {% if nav_active == 'bots' %}class="active"{% endif %}><span class="ico"><i class="fa-solid fa-robot"></i></span><span class="lbl">Bots</span></a>
<a href="/rss-bots" {% if nav_active == 'rss-bots' %}class="active"{% endif %}><span class="ico"><i class="fa-solid fa-rss"></i></span><span class="lbl">RSS Bots</span></a>
<a href="/businesses" {% if nav_active == 'businesses' %}class="active"{% endif %}><span class="ico"><i class="fa-solid fa-briefcase"></i></span><span class="lbl">Business Groups</span></a> <a href="/businesses" {% if nav_active == 'businesses' %}class="active"{% endif %}><span class="ico"><i class="fa-solid fa-briefcase"></i></span><span class="lbl">Business Groups</span></a>
<a href="https://simplex.chat/file/" target="_blank" rel="noopener"><span class="ico"><i class="fa-solid fa-upload"></i></span><span class="lbl">File Upload</span></a> <a href="https://simplex.chat/file/" target="_blank" rel="noopener"><span class="ico"><i class="fa-solid fa-upload"></i></span><span class="lbl">File Upload</span></a>
<!-- Group 2: relays --> <!-- Group 2: relays -->

View File

@@ -40,6 +40,10 @@
<span class="t-ico"><i class="fa-solid fa-robot"></i></span> <span class="t-ico"><i class="fa-solid fa-robot"></i></span>
<span class="t-title">Bots</span> <span class="t-title">Bots</span>
</a> </a>
<a class="tile" href="/rss-bots">
<span class="t-ico"><i class="fa-solid fa-rss"></i></span>
<span class="t-title">RSS Bots</span>
</a>
<a class="tile" href="/businesses"> <a class="tile" href="/businesses">
<span class="t-ico"><i class="fa-solid fa-briefcase"></i></span> <span class="t-ico"><i class="fa-solid fa-briefcase"></i></span>
<span class="t-title">Business Groups</span> <span class="t-title">Business Groups</span>

View File

@@ -1,6 +1,6 @@
{% extends "base.html" %} {% extends "base.html" %}
{% import "_macros.html" as ui %} {% import "_macros.html" as ui %}
{% block title %}{{ 'Business Groups' if tab == 'businesses' else tab | title }} — SimpleX Manager{% endblock %} {% block title %}{{ 'Business Groups' if tab == 'businesses' else ('RSS Bots' if tab == 'rss-bots' else tab | title) }} — SimpleX Manager{% endblock %}
{% block head %} {% block head %}
<style> <style>
@@ -29,8 +29,8 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
{% set new_label = 'User' if tab == 'users' else ('Business Group' if tab == 'businesses' else 'Bot') %} {% set new_label = 'User' if tab == 'users' else ('Business Group' if tab == 'businesses' else ('RSS Bot' if tab == 'rss-bots' else 'Bot')) %}
{% set page_title = 'Business Groups' if tab == 'businesses' else tab | title %} {% set page_title = 'Business Groups' if tab == 'businesses' else ('RSS Bots' if tab == 'rss-bots' else tab | title) %}
<div class="flex-between" style="margin-bottom: 24px;"> <div class="flex-between" style="margin-bottom: 24px;">
<h1 style="margin:0;">{{ page_title }}</h1> <h1 style="margin:0;">{{ page_title }}</h1>
<button class="btn btn-primary" onclick="openCreate()"> <button class="btn btn-primary" onclick="openCreate()">
@@ -49,13 +49,23 @@
</div> </div>
{% endif %} {% endif %}
{% if tab == 'rss-bots' %}
<div class="card bot-types-card" style="margin-bottom:24px;">
<h2 style="font-size:15px;margin-bottom:8px;">RSS Bots</h2>
<p class="muted" style="font-size:13px;">
RSS bots read from an RSS/Atom feed and post new items to a channel. To receive a feed,
share the bot's <strong>channel</strong> link with subscribers — open the bot and copy its
<strong>channel</strong> link, <strong>not</strong> the user address.
</p>
</div>
{% endif %}
{% if tab == 'bots' %} {% if tab == 'bots' %}
<div class="card bot-types-card" style="margin-bottom:24px;"> <div class="card bot-types-card" style="margin-bottom:24px;">
<h2 style="font-size:15px;margin-bottom:12px;">Available bot types</h2> <h2 style="font-size:15px;margin-bottom:12px;">Available bot types</h2>
<table> <table>
<tr><td><span class="tag">echo</span></td><td class="muted">Repeats every message back to the sender — handy for testing a connection end to end.</td></tr> <tr><td><span class="tag">echo</span></td><td class="muted">Repeats every message back to the sender — handy for testing a connection end to end.</td></tr>
<tr><td><span class="tag">llm</span></td><td class="muted">Chat with a local or remote LLM (OpenAI-compatible, e.g. Ollama). Give it context, it replies to your messages.</td></tr> <tr><td><span class="tag">llm</span></td><td class="muted">Chat with a local or remote LLM (OpenAI-compatible, e.g. Ollama). Give it context, it replies to your messages.</td></tr>
<tr><td><span class="tag">rss</span></td><td class="muted">Watches an RSS/Atom feed and broadcasts new posts to a channel it creates. Subscribers join the channel to receive them.</td></tr>
<tr><td><span class="tag">crypto</span></td><td class="muted">Streams selected crypto prices (CoinGecko) to a channel on an interval. Pick coins &amp; currencies below.</td></tr> <tr><td><span class="tag">crypto</span></td><td class="muted">Streams selected crypto prices (CoinGecko) to a channel on an interval. Pick coins &amp; currencies below.</td></tr>
<tr><td><span class="tag">broadcast</span></td><td class="muted">Relays messages from authorized publishers out to all of the bot's contacts.</td></tr> <tr><td><span class="tag">broadcast</span></td><td class="muted">Relays messages from authorized publishers out to all of the bot's contacts.</td></tr>
<tr><td><span class="tag">support</span></td><td class="muted">Business inbox — auto-replies with a welcome message and collects incoming inquiries.</td></tr> <tr><td><span class="tag">support</span></td><td class="muted">Business inbox — auto-replies with a welcome message and collects incoming inquiries.</td></tr>
@@ -108,6 +118,9 @@
{% elif tab == 'businesses' %} {% elif tab == 'businesses' %}
<strong>No business groups yet</strong> <strong>No business groups yet</strong>
<p>Create a business group; each customer who connects gets their own group chat.</p> <p>Create a business group; each customer who connects gets their own group chat.</p>
{% elif tab == 'rss-bots' %}
<strong>No RSS bots yet</strong>
<p>Create an RSS bot to post a feed to a channel.</p>
{% else %} {% else %}
<strong>No bots yet</strong> <strong>No bots yet</strong>
<p>Bots can echo messages, broadcast to subscribers, or run automated tasks.</p> <p>Bots can echo messages, broadcast to subscribers, or run automated tasks.</p>
@@ -140,6 +153,20 @@
<input type="text" name="welcome_message" placeholder="Thanks for reaching out! How can we help?"> <input type="text" name="welcome_message" placeholder="Thanks for reaching out! How can we help?">
</div> </div>
{% endif %} {% endif %}
{% if tab == 'rss-bots' %}
<div class="field">
<label>Feed URL</label>
<input type="text" name="feed_url" placeholder="https://example.com/feed.xml">
</div>
<div class="field">
<label>How often to check the feed</label>
<div class="chk-grid">
<label class="chk"><input type="radio" name="rss_poll" value="3600" checked> Per hour</label>
<label class="chk"><input type="radio" name="rss_poll" value="86400"> Per day</label>
<label class="chk"><input type="radio" name="rss_poll" value="604800"> Per week</label>
</div>
</div>
{% endif %}
{% if tab == 'bots' %} {% if tab == 'bots' %}
<div class="field"> <div class="field">
<label>Bot Type</label> <label>Bot Type</label>
@@ -228,27 +255,6 @@
<input type="text" name="prohibited_message" placeholder="Only publishers can broadcast. Your message is deleted."> <input type="text" name="prohibited_message" placeholder="Only publishers can broadcast. Your message is deleted.">
</div> </div>
</div> </div>
<div id="rss-fields" style="display:none;">
<div style="border-top:1px solid var(--border);margin:4px 0 14px;padding-top:14px;">
<p class="muted" style="margin-bottom:12px;">
The bot watches this feed and broadcasts new posts to a channel it creates.
Share the channel link (from the bot's profile) with subscribers — new posts
appear there automatically.
</p>
</div>
<div class="field">
<label>Feed URL</label>
<input type="text" name="feed_url" placeholder="https://example.com/feed.xml">
</div>
<div class="field">
<label>How often to check the feed</label>
<div class="chk-grid">
<label class="chk"><input type="radio" name="rss_poll" value="3600" checked> Per hour</label>
<label class="chk"><input type="radio" name="rss_poll" value="86400"> Per day</label>
<label class="chk"><input type="radio" name="rss_poll" value="604800"> Per week</label>
</div>
</div>
</div>
<div id="crypto-fields" style="display:none;"> <div id="crypto-fields" style="display:none;">
<div style="border-top:1px solid var(--border);margin:4px 0 14px;padding-top:14px;"> <div style="border-top:1px solid var(--border);margin:4px 0 14px;padding-top:14px;">
<p class="muted" style="margin-bottom:12px;"> <p class="muted" style="margin-bottom:12px;">
@@ -355,7 +361,6 @@ function onTypeChange() {
document.getElementById('deadmans-fields').style.display = (val === 'deadmans') ? 'block' : 'none'; document.getElementById('deadmans-fields').style.display = (val === 'deadmans') ? 'block' : 'none';
document.getElementById('directory-fields').style.display = (val === 'directory') ? 'block' : 'none'; document.getElementById('directory-fields').style.display = (val === 'directory') ? 'block' : 'none';
document.getElementById('broadcast-fields').style.display = (val === 'broadcast') ? 'block' : 'none'; document.getElementById('broadcast-fields').style.display = (val === 'broadcast') ? 'block' : 'none';
document.getElementById('rss-fields').style.display = (val === 'rss') ? 'block' : 'none';
document.getElementById('crypto-fields').style.display = (val === 'crypto') ? 'block' : 'none'; document.getElementById('crypto-fields').style.display = (val === 'crypto') ? 'block' : 'none';
} }
{% endif %} {% endif %}