decentralized and customizable links page on top of atproto ligo.at
atproto link-in-bio python uv
9
fork

Configure Feed

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

change typeahead provider

+34 -13
+20 -11
packages/xrpc/src/xrpc/__init__.py
··· 1 1 from urllib.parse import urljoin 2 2 3 3 from aiohttp.client import ClientSession 4 - from flask import Blueprint, request 4 + from flask import Blueprint, current_app, request 5 5 6 6 xrpc = Blueprint("xrpc", __name__, url_prefix="/xrpc") 7 7 8 8 9 9 @xrpc.get("/app.bsky.actor.searchActorsTypeahead") 10 10 async def search_actors_typeahead(): 11 - base = "https://public.api.bsky.app" 11 + client_header = request.headers.get("X-Client") 12 + if not client_header: 13 + return ("missing X-Client header", 400) 14 + base = "https://typeahead.waow.tech" 12 15 url = urljoin(base, "xrpc/app.bsky.actor.searchActorsTypeahead") 13 16 url += f"?{request.query_string.decode('utf-8')}" 14 17 async with ClientSession() as client: 15 - async with client.get(url) as response: 16 - json = await response.json() 18 + if not current_app.debug: 19 + client.headers.add("X-Client", client_header) 20 + async with client.get(url) as typeahead_response: 21 + response = await typeahead_response.json() 22 + status = 200 17 23 try: 18 - res = [ 19 - {"avatar": actor["avatar"], "handle": actor["handle"]} 20 - for actor in json["actors"] 24 + actors = [ 25 + {"avatar": actor.get("avatar"), "handle": actor["handle"]} 26 + for actor in response["actors"] 21 27 ] 22 - return { 23 - "actors": res, 28 + response = { 29 + "actors": actors, 24 30 } 25 - except KeyError: 26 - return json 31 + except (KeyError, TypeError) as error: 32 + current_app.logger.error(error) 33 + response = {"actors": []} 34 + status = 500 35 + return (response, status)
+13 -1
src/static/actor-typeahead.js
··· 6 6 * Copyright (c) 2026 ligo.at contributors 7 7 * 8 8 * Changes 9 + * 2026-03-19 10 + * New: Attribute `client` to send via the `X-Client` HTTP header. 9 11 * 2026-03-17 10 12 * Fix: Abort previous HTTP request before starting the next. 11 13 * New: Add 250ms debounce to oninput. ··· 127 129 128 130 /** 129 131 * @attribute {string} [host] - The host to which to make the typeahead API call. If set to "location" it uses window.location. 132 + * @attribute {string} [client] - The optional value to send in the X-Client HTTP header. 130 133 * @attribute {number} [rows] - The maximum number of rows to display in the dropdown. 131 134 * 132 135 * @csspart menu - The dropdown menu. ··· 327 330 this.#controller?.abort(); 328 331 this.#controller = new AbortController(); 329 332 333 + const headers = {}; 334 + const client = this.getAttribute("client"); 335 + if (client) { 336 + headers["X-Client"] = client; 337 + } 338 + 330 339 try { 331 - const res = await fetch(url, { signal: this.#controller.signal }); 340 + const res = await fetch(url, { 341 + headers, 342 + signal: this.#controller.signal, 343 + }); 332 344 const json = await res.json(); 333 345 this.#actors = json.actors; 334 346 this.#index = -1;
+1 -1
src/templates/login.html
··· 21 21 <form action="{{ url_for('auth_login') }}" method="post"> 22 22 <label> 23 23 <span>Handle</span> 24 - <actor-typeahead host="location"> 24 + <actor-typeahead host="location" client="ligo.at"> 25 25 <input 26 26 type="text" 27 27 name="username"