Add 'crypto' bot: streams CoinGecko prices to a channel
New crypto bot type: creates a broadcast channel and posts a price snapshot of the selected coins/currencies (CoinGecko simple/price JSON) every interval — same channel-streaming model as RSS. Create form has checkbox grids for popular coins and currencies plus a poll interval. Generalize the channel helper and feed-poll state (channel_gid/poll_next) shared by rss + crypto. Adds crypto_test.py (mock CoinGecko) — passes; rss_test updated for the renamed field. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,11 @@
|
||||
|
||||
.bot-types-card table td { vertical-align: top; }
|
||||
.bot-types-card .tag { white-space: nowrap; }
|
||||
|
||||
.chk-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 6px 12px; }
|
||||
.chk { display: flex; align-items: center; gap: 7px; font-size: 13px; font-weight: 500;
|
||||
color: var(--text); cursor: pointer; }
|
||||
.chk input { width: auto; }
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
@@ -50,6 +55,7 @@
|
||||
<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">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 & 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">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">directory</span></td><td class="muted">Directory service for discovering and listing groups or contacts.</td></tr>
|
||||
@@ -242,6 +248,45 @@
|
||||
<input type="number" name="poll_seconds" min="30" value="300">
|
||||
</div>
|
||||
</div>
|
||||
<div id="crypto-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;">
|
||||
Posts a price snapshot of the selected coins to a channel every interval (via CoinGecko).
|
||||
</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Coins</label>
|
||||
<div class="chk-grid">
|
||||
<label class="chk"><input type="checkbox" name="coin" value="bitcoin" checked> Bitcoin</label>
|
||||
<label class="chk"><input type="checkbox" name="coin" value="ethereum" checked> Ethereum</label>
|
||||
<label class="chk"><input type="checkbox" name="coin" value="solana"> Solana</label>
|
||||
<label class="chk"><input type="checkbox" name="coin" value="ripple"> XRP</label>
|
||||
<label class="chk"><input type="checkbox" name="coin" value="cardano"> Cardano</label>
|
||||
<label class="chk"><input type="checkbox" name="coin" value="dogecoin"> Dogecoin</label>
|
||||
<label class="chk"><input type="checkbox" name="coin" value="binancecoin"> BNB</label>
|
||||
<label class="chk"><input type="checkbox" name="coin" value="polkadot"> Polkadot</label>
|
||||
<label class="chk"><input type="checkbox" name="coin" value="litecoin"> Litecoin</label>
|
||||
<label class="chk"><input type="checkbox" name="coin" value="tron"> TRON</label>
|
||||
<label class="chk"><input type="checkbox" name="coin" value="chainlink"> Chainlink</label>
|
||||
<label class="chk"><input type="checkbox" name="coin" value="tether"> Tether</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Currencies</label>
|
||||
<div class="chk-grid">
|
||||
<label class="chk"><input type="checkbox" name="cur" value="usd" checked> USD</label>
|
||||
<label class="chk"><input type="checkbox" name="cur" value="gbp" checked> GBP</label>
|
||||
<label class="chk"><input type="checkbox" name="cur" value="eur"> EUR</label>
|
||||
<label class="chk"><input type="checkbox" name="cur" value="jpy"> JPY</label>
|
||||
<label class="chk"><input type="checkbox" name="cur" value="aud"> AUD</label>
|
||||
<label class="chk"><input type="checkbox" name="cur" value="cad"> CAD</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Poll interval <span class="muted" style="font-weight:400;">(seconds)</span></label>
|
||||
<input type="number" name="crypto_poll_seconds" min="60" value="300">
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="flex gap-8 mt-16" style="justify-content:flex-end;">
|
||||
<button type="button" class="btn btn-ghost"
|
||||
@@ -330,6 +375,7 @@ function onTypeChange() {
|
||||
document.getElementById('directory-fields').style.display = (val === 'directory') ? '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';
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
@@ -381,6 +427,16 @@ document.getElementById('create-form').addEventListener('submit', async (e) => {
|
||||
const ps = parseInt(fd.get('poll_seconds'), 10);
|
||||
if (!isNaN(ps) && ps >= 30) config.poll_seconds = ps;
|
||||
}
|
||||
if (botType === 'crypto') {
|
||||
const coins = Array.from(document.querySelectorAll('#crypto-fields input[name=coin]:checked')).map(c => c.value);
|
||||
const curs = Array.from(document.querySelectorAll('#crypto-fields input[name=cur]:checked')).map(c => c.value);
|
||||
if (!coins.length) { alert('Pick at least one coin'); return; }
|
||||
if (!curs.length) { alert('Pick at least one currency'); return; }
|
||||
config.coins = coins;
|
||||
config.currencies = curs;
|
||||
const ps = parseInt(fd.get('crypto_poll_seconds'), 10);
|
||||
if (!isNaN(ps) && ps >= 60) config.poll_seconds = ps;
|
||||
}
|
||||
{% endif %}
|
||||
// Shared profile fields (users and bots)
|
||||
const bio = (fd.get('bio') || '').trim();
|
||||
|
||||
Reference in New Issue
Block a user