Retro Bulletin Board Systems on atproto. Web app and TUI. atbbs.xyz
python tui atproto bbs
at master 106 lines 3.5 kB view raw
1import asyncio 2 3import httpx 4 5from core.models import ( 6 AtUri, 7 BBS, 8 Board, 9 News, 10 Site, 11 BBSNotFoundError, 12 NoBBSError, 13 NetworkError, 14) 15from core import lexicon 16from core.constellation import get_news 17from core.records import list_pds_records 18from core.slingshot import get_record, get_records_batch, resolve_identity 19 20 21async def resolve_bbs(client: httpx.AsyncClient, handle: str) -> BBS: 22 """Handle -> fully resolved BBS config.""" 23 try: 24 identity = await resolve_identity(client, handle) 25 except httpx.HTTPStatusError: 26 raise BBSNotFoundError(f"Could not resolve handle: {handle}") 27 except httpx.TransportError: 28 raise NetworkError("Could not reach the network.") 29 30 try: 31 site_record = await get_record(client, identity.did, lexicon.SITE, "self") 32 except httpx.HTTPStatusError: 33 raise NoBBSError(f"{handle} isn't running a BBS.") 34 except httpx.TransportError: 35 raise NetworkError("Could not reach the network.") 36 37 sv = site_record.value 38 site_uri = str(AtUri(identity.did, lexicon.SITE, "self")) 39 40 # Fetch boards, news, bans, and hidden posts concurrently 41 board_slugs = sv["boards"] 42 board_tasks = [ 43 get_record(client, identity.did, lexicon.BOARD, slug) for slug in board_slugs 44 ] 45 news_task = get_news(client, site_uri) 46 ban_task = list_pds_records(client, identity.pds, identity.did, lexicon.BAN) 47 hidden_task = list_pds_records(client, identity.pds, identity.did, lexicon.HIDE) 48 49 results = await asyncio.gather( 50 *board_tasks, news_task, ban_task, hidden_task, return_exceptions=True 51 ) 52 board_records = results[: len(board_slugs)] 53 news_result = results[len(board_slugs)] 54 ban_result = results[len(board_slugs) + 1] 55 hidden_result = results[len(board_slugs) + 2] 56 57 boards = [ 58 Board( 59 slug=slug, 60 name=r.value["name"], 61 description=r.value["description"], 62 created_at=r.value["createdAt"], 63 updated_at=r.value.get("updatedAt"), 64 ) 65 for slug, r in zip(board_slugs, board_records) 66 if not isinstance(r, BaseException) 67 ] 68 69 # Hydrate news records (only from the sysop's repo) 70 if isinstance(news_result, BaseException): 71 news_records = [] 72 else: 73 sysop_news = [r for r in news_result.records if r.did == identity.did] 74 news_records = await get_records_batch(client, sysop_news) 75 news = [ 76 News( 77 tid=AtUri.parse(r.uri).rkey, 78 site_uri=r.value["site"], 79 title=r.value["title"], 80 body=r.value["body"], 81 created_at=r.value["createdAt"], 82 ) 83 for r in news_records 84 ] 85 news.sort(key=lambda n: n.created_at, reverse=True) 86 87 # Build ban/hidden sets from standalone records 88 banned_dids: set[str] = set() 89 if not isinstance(ban_result, BaseException): 90 banned_dids = {r["value"]["did"] for r in ban_result} 91 hidden_posts: set[str] = set() 92 if not isinstance(hidden_result, BaseException): 93 hidden_posts = {r["value"]["uri"] for r in hidden_result} 94 95 site = Site( 96 name=sv["name"], 97 description=sv["description"], 98 intro=sv["intro"], 99 boards=boards, 100 banned_dids=banned_dids, 101 hidden_posts=hidden_posts, 102 created_at=sv.get("createdAt", ""), 103 updated_at=sv.get("updatedAt"), 104 ) 105 106 return BBS(identity=identity, site=site, news=news)