semantic bufo search find-bufo.com
bufo
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: add posting strategy, contribution CTA to stats page + CONTRIBUTING.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

zzstoatzz 2d07d3b1 1be826a2

+82 -1
+25
CONTRIBUTING.md
··· 1 + # contributing to find-bufo 2 + 3 + ## what is a bufo? 4 + 5 + bufos are frog images from [bufo.zone](https://all-the.bufo.zone). find-bufo is a bluesky bot that watches the firehose and quote-posts with a matching bufo image when someone's post contains a bufo phrase. 6 + 7 + ## submitting a new bufo 8 + 9 + ### naming 10 + 11 + the filename **is** the matching phrase. `bufo-jumping-for-joy.png` matches posts containing "jumping for joy". 12 + 13 + - use kebab-case: `bufo-descriptive-phrase.png` 14 + - `.png` or `.gif` only 15 + - keep it descriptive — the phrase should make sense as a natural match 16 + 17 + ### how to submit 18 + 19 + 1. fork the repo on [tangled.org](https://tangled.org/zzstoatzz.io/find-bufo) 20 + 2. add your image to `data/bufos/` 21 + 3. open a PR with a screenshot or preview of the bufo in action 22 + 23 + ### what happens next 24 + 25 + a maintainer runs the ingestion script (`scripts/add_one_bufo.py`) to embed and index your image. once merged and deployed, the bot starts matching posts with your bufo's phrase.
+57 -1
bot/src/stats_template.zig
··· 39 39 \\ .excluded-label {{ color: #666; }} 40 40 \\ .excluded-value {{ color: #888; }} 41 41 \\ h2 {{ color: #7bed9f; margin-top: 40px; font-size: 1.2em; }} 42 + \\ .strategy {{ 43 + \\ margin-top: 30px; 44 + \\ padding: 16px; 45 + \\ background: #252542; 46 + \\ border-radius: 8px; 47 + \\ font-size: 0.9em; 48 + \\ line-height: 1.6; 49 + \\ }} 50 + \\ .strategy p {{ color: #aaa; margin: 0 0 12px 0; }} 51 + \\ .strategy p:last-child {{ margin-bottom: 0; }} 52 + \\ .strategy-rates {{ 53 + \\ display: flex; 54 + \\ gap: 16px; 55 + \\ flex-wrap: wrap; 56 + \\ margin-top: 8px; 57 + \\ }} 58 + \\ .strategy-rate {{ 59 + \\ color: #aaa; 60 + \\ font-size: 0.85em; 61 + \\ }} 62 + \\ .strategy-rate span {{ color: #7bed9f; font-weight: bold; }} 63 + \\ .cta {{ 64 + \\ margin-top: 30px; 65 + \\ padding: 16px; 66 + \\ background: #252542; 67 + \\ border-radius: 8px; 68 + \\ text-align: center; 69 + \\ }} 70 + \\ .cta p {{ color: #aaa; margin: 8px 0 0; font-size: 0.9em; }} 42 71 \\ .bufo-grid {{ 43 72 \\ display: flex; 44 73 \\ flex-wrap: wrap; ··· 218 247 \\ <span class="excluded-value">posts with nsfw <a href="https://docs.bsky.app/docs/advanced-guides/moderation#labels">labels</a> or keywords</span> 219 248 \\</div> 220 249 \\ 250 + \\<div class="strategy"> 251 + \\ <h2 style="margin-top:0">posting strategy</h2> 252 + \\ <p>global cooldown of 30 min between any post, plus per-bufo scaling &mdash; 253 + \\ bufos that match more often get longer cooldowns (quadratic: a bufo at 30% of matches waits ~10x longer).</p> 254 + \\ <div class="strategy-rates" id="strategy-rates"></div> 255 + \\</div> 256 + \\ 221 257 \\<div class="lookup"> 222 258 \\ <h2>user lookup</h2> 223 259 \\ <div class="lookup-form"> ··· 238 274 \\{s} 239 275 \\</div> 240 276 \\ 277 + \\<div class="cta"> 278 + \\ <h2 style="margin-top:0">add your own bufo</h2> 279 + \\ <p>have a bufo that should trigger find-bufo? <a href="https://tangled.org/zzstoatzz.io/find-bufo/blob/main/CONTRIBUTING.md">submit it via pull request</a></p> 280 + \\</div> 281 + \\ 241 282 \\<div class="footer"> 242 283 \\ <a href="https://find-bufo.com">find-bufo.com</a> · 243 284 \\ <a href="https://bsky.app/profile/find-bufo.com">@find-bufo.com</a> · ··· 257 298 \\</div> 258 299 \\<script> 259 300 \\(function() {{ 301 + \\ const nums = {{}}; 260 302 \\ document.querySelectorAll('[data-num]').forEach(el => {{ 261 - \\ el.textContent = parseInt(el.dataset.num).toLocaleString(); 303 + \\ const v = parseInt(el.dataset.num); 304 + \\ nums[el.previousElementSibling?.textContent?.trim()] = v; 305 + \\ el.textContent = v.toLocaleString(); 262 306 \\ }}); 307 + \\ const pct = (a, b) => b > 0 ? (100 * a / b).toFixed(1) + '%' : '—'; 308 + \\ const ratesEl = document.getElementById('strategy-rates'); 309 + \\ if (ratesEl) {{ 310 + \\ const checked = nums['posts checked'] || 0; 311 + \\ const matches = nums['matches found'] || 0; 312 + \\ const posted = nums['bufos posted'] || 0; 313 + \\ const cooldowns = nums['cooldowns hit'] || 0; 314 + \\ ratesEl.innerHTML = 315 + \\ '<div class="strategy-rate">match rate <span>' + pct(matches, checked) + '</span></div>' + 316 + \\ '<div class="strategy-rate">post rate <span>' + pct(posted, matches) + '</span></div>' + 317 + \\ '<div class="strategy-rate">cooldown rate <span>' + pct(cooldowns, matches) + '</span></div>'; 318 + \\ }} 263 319 \\ const uptimeEl = document.getElementById('uptime'); 264 320 \\ let secs = parseInt(uptimeEl.dataset.seconds); 265 321 \\ function fmt(s) {{