data endpoint for entity 90008 (aka. a website)
at svelte 3.6 kB view raw
1import { get, writable } from 'svelte/store'; 2import { parseFeed } from '@rowanmanning/feed-parser'; 3 4const lastCommits = writable<Activity[]>([]); 5 6export const updateCommits = async () => { 7 try { 8 const githubFeed = await parseFeedToActivity('https://github.com/90-008.atom'); 9 const codebergFeed = await parseFeedToActivity('https://codeberg.org/90-008.atom'); 10 const tangledFeed = await fetchTangledActivity(); 11 const mergedFeed = sortActivities( 12 githubFeed.concat(codebergFeed).concat(tangledFeed) 13 ).slice(0, 7); 14 lastCommits.set(mergedFeed); 15 } catch (why) { 16 console.log('could not fetch git activity: ', why); 17 } 18}; 19 20export const getLastActivity = () => { 21 return get(lastCommits); 22}; 23 24type Activity = { 25 source: string; 26 description: string; 27 link: string | null; 28 date: Date | null; 29}; 30 31const toHex = (bytes: number[]): string => { 32 return bytes.map((b) => b.toString(16).padStart(2, '0')).join(''); 33}; 34 35const fetchTangledActivity = async (): Promise<Activity[]> => { 36 // todo: auto resolve pds and knots 37 const did = 'did:plc:dfl62fgb7wtjj3fcbb72naae'; 38 const pds = 'https://zwsp.xyz'; 39 const knot = 'https://knot.gaze.systems'; 40 const activities: Activity[] = []; 41 42 try { 43 // todo: fetch until we exhaust 44 const listRes = await fetch( 45 `${pds}/xrpc/com.atproto.repo.listRecords?repo=${did}&collection=sh.tangled.repo` 46 ); 47 if (!listRes.ok) return []; 48 const listData = await listRes.json(); 49 50 for (const record of listData.records || []) { 51 const repoName = record.value.name; 52 if (!repoName) continue; 53 54 try { 55 const logRes = await fetch( 56 `${knot}/xrpc/sh.tangled.repo.log?repo=${did}/${repoName}` 57 ); 58 if (!logRes.ok) continue; 59 const logData = await logRes.json(); 60 61 const commits = logData.commits || []; 62 63 for (const commit of commits) { 64 const hash = commit.Hash ? toHex(commit.Hash) : ''; 65 const message = commit.Message || ''; 66 const dateStr = commit.Author?.When; 67 68 activities.push({ 69 source: 'tangled', 70 description: `pushed ${repoName}: ${message}`, 71 link: `https://tangled.sh/${did}/${repoName}/commit/${hash}`, 72 date: dateStr ? new Date(dateStr) : null 73 }); 74 } 75 } catch (err) { 76 console.log(`could not fetch tangled log for ${repoName}:`, err); 77 } 78 } 79 } catch (err) { 80 console.log('could not fetch tangled repos:', err); 81 } 82 return activities; 83}; 84 85const parseFeedToActivity = async (url: string) => { 86 const response = await fetch(url); 87 const feed = parseFeed(await response.text()); 88 89 const source = new URL(url).host.split('.')[0]; 90 const results: Activity[] = []; 91 for (const item of feed.items) { 92 const description: string | null = item.description || item.title; 93 if (description === null) continue; 94 // dont count mirrored repos 95 // TODO: probably can implement a deduplication algorithm 96 if ( 97 ['90-008/ark', '90-008/website', 'ark', 'website'].some((repo) => 98 description.includes(repo) 99 ) 100 ) 101 continue; 102 // dont show activity that is just chore 103 if (item.content?.includes('chore')) continue; 104 const desc = description.split('</a>').at(1) || description.split('</a>').pop() || ''; 105 results.push({ 106 source, 107 description: desc.replace(/^90-008 /, ""), 108 link: item.url, 109 date: item.published || item.updated 110 }); 111 } 112 113 return results; 114}; 115 116const sortActivities = (activities: Array<Activity>) => { 117 return activities.sort((a, b) => { 118 if (a.date === null && b.date === null) return 0; 119 if (a.date === null) return 1; 120 if (b.date === null) return -1; 121 return b.date.getTime() - a.date.getTime(); 122 }); 123};