| 
									
										
										
										
											2023-07-27 01:47:50 +00:00
										 |  |  | let g_about_cache = {}; | 
					
						
							| 
									
										
										
										
											2022-08-04 00:57:56 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-27 01:47:50 +00:00
										 |  |  | async function query(sql, args) { | 
					
						
							|  |  |  | 	let result = []; | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  | 	await ssb.sqlAsync(sql, args, function (row) { | 
					
						
							| 
									
										
										
										
											2023-07-27 01:47:50 +00:00
										 |  |  | 		result.push(row); | 
					
						
							|  |  |  | 	}); | 
					
						
							|  |  |  | 	return result; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | async function contacts_internal(id, last_row_id, following, max_row_id) { | 
					
						
							|  |  |  | 	let result = Object.assign({}, following[id] || {}); | 
					
						
							|  |  |  | 	result.following = result.following || {}; | 
					
						
							|  |  |  | 	result.blocking = result.blocking || {}; | 
					
						
							|  |  |  | 	let contacts = await query( | 
					
						
							|  |  |  | 		`
 | 
					
						
							| 
									
										
										
										
											2025-01-01 15:28:19 -05:00
										 |  |  | 				SELECT json(content) AS content FROM messages | 
					
						
							| 
									
										
										
										
											2023-07-27 01:47:50 +00:00
										 |  |  | 				WHERE author = ? AND | 
					
						
							|  |  |  | 				rowid > ? AND | 
					
						
							|  |  |  | 				rowid <= ? AND | 
					
						
							|  |  |  | 				json_extract(content, '$.type') = 'contact' | 
					
						
							|  |  |  | 				ORDER BY sequence | 
					
						
							|  |  |  | 			`,
 | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  | 		[id, last_row_id, max_row_id] | 
					
						
							|  |  |  | 	); | 
					
						
							| 
									
										
										
										
											2023-07-27 01:47:50 +00:00
										 |  |  | 	for (let row of contacts) { | 
					
						
							|  |  |  | 		let contact = JSON.parse(row.content); | 
					
						
							|  |  |  | 		if (contact.following === true) { | 
					
						
							|  |  |  | 			result.following[contact.contact] = true; | 
					
						
							|  |  |  | 		} else if (contact.following === false) { | 
					
						
							|  |  |  | 			delete result.following[contact.contact]; | 
					
						
							|  |  |  | 		} else if (contact.blocking === true) { | 
					
						
							|  |  |  | 			result.blocking[contact.contact] = true; | 
					
						
							|  |  |  | 		} else if (contact.blocking === false) { | 
					
						
							|  |  |  | 			delete result.blocking[contact.contact]; | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2022-08-04 00:57:56 +00:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-07-27 01:47:50 +00:00
										 |  |  | 	following[id] = result; | 
					
						
							|  |  |  | 	return result; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | async function contact(id, last_row_id, following, max_row_id) { | 
					
						
							|  |  |  | 	return await contacts_internal(id, last_row_id, following, max_row_id); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  | async function following_deep_internal( | 
					
						
							|  |  |  | 	ids, | 
					
						
							|  |  |  | 	depth, | 
					
						
							|  |  |  | 	blocking, | 
					
						
							|  |  |  | 	last_row_id, | 
					
						
							|  |  |  | 	following, | 
					
						
							|  |  |  | 	max_row_id | 
					
						
							|  |  |  | ) { | 
					
						
							|  |  |  | 	let contacts = await Promise.all( | 
					
						
							|  |  |  | 		[...new Set(ids)].map((x) => contact(x, last_row_id, following, max_row_id)) | 
					
						
							|  |  |  | 	); | 
					
						
							| 
									
										
										
										
											2023-07-27 01:47:50 +00:00
										 |  |  | 	let result = {}; | 
					
						
							|  |  |  | 	for (let i = 0; i < ids.length; i++) { | 
					
						
							|  |  |  | 		let id = ids[i]; | 
					
						
							|  |  |  | 		let contact = contacts[i]; | 
					
						
							|  |  |  | 		let all_blocking = Object.assign({}, contact.blocking, blocking); | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  | 		let found = Object.keys(contact.following).filter((y) => !all_blocking[y]); | 
					
						
							|  |  |  | 		let deeper = | 
					
						
							|  |  |  | 			depth > 1 | 
					
						
							|  |  |  | 				? await following_deep_internal( | 
					
						
							|  |  |  | 						found, | 
					
						
							|  |  |  | 						depth - 1, | 
					
						
							|  |  |  | 						all_blocking, | 
					
						
							|  |  |  | 						last_row_id, | 
					
						
							|  |  |  | 						following, | 
					
						
							|  |  |  | 						max_row_id | 
					
						
							|  |  |  | 					) | 
					
						
							|  |  |  | 				: []; | 
					
						
							| 
									
										
										
										
											2023-07-27 01:47:50 +00:00
										 |  |  | 		result[id] = [id, ...found, ...deeper]; | 
					
						
							| 
									
										
										
										
											2022-08-04 00:57:56 +00:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-07-27 01:47:50 +00:00
										 |  |  | 	return [...new Set(Object.values(result).flat())]; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | async function following_deep(ids, depth, blocking) { | 
					
						
							|  |  |  | 	let db = await database('cache'); | 
					
						
							|  |  |  | 	const k_cache_version = 5; | 
					
						
							|  |  |  | 	let cache = await db.get('following'); | 
					
						
							|  |  |  | 	cache = cache ? JSON.parse(cache) : {}; | 
					
						
							|  |  |  | 	if (cache.version !== k_cache_version) { | 
					
						
							|  |  |  | 		cache = { | 
					
						
							|  |  |  | 			version: k_cache_version, | 
					
						
							|  |  |  | 			following: {}, | 
					
						
							|  |  |  | 			last_row_id: 0, | 
					
						
							|  |  |  | 		}; | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  | 	let max_row_id = ( | 
					
						
							|  |  |  | 		await query( | 
					
						
							|  |  |  | 			`
 | 
					
						
							| 
									
										
										
										
											2023-07-27 01:47:50 +00:00
										 |  |  | 			SELECT MAX(rowid) AS max_row_id FROM messages | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  | 		`,
 | 
					
						
							|  |  |  | 			[] | 
					
						
							|  |  |  | 		) | 
					
						
							|  |  |  | 	)[0].max_row_id; | 
					
						
							|  |  |  | 	let result = await following_deep_internal( | 
					
						
							|  |  |  | 		ids, | 
					
						
							|  |  |  | 		depth, | 
					
						
							|  |  |  | 		blocking, | 
					
						
							|  |  |  | 		cache.last_row_id, | 
					
						
							|  |  |  | 		cache.following, | 
					
						
							|  |  |  | 		max_row_id | 
					
						
							|  |  |  | 	); | 
					
						
							| 
									
										
										
										
											2023-07-27 01:47:50 +00:00
										 |  |  | 	cache.last_row_id = max_row_id; | 
					
						
							|  |  |  | 	let store = JSON.stringify(cache); | 
					
						
							|  |  |  | 	await db.set('following', store); | 
					
						
							|  |  |  | 	return result; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | async function fetch_about(db, ids, users) { | 
					
						
							|  |  |  | 	const k_cache_version = 1; | 
					
						
							|  |  |  | 	let cache = await db.get('about'); | 
					
						
							|  |  |  | 	cache = cache ? JSON.parse(cache) : {}; | 
					
						
							|  |  |  | 	if (cache.version !== k_cache_version) { | 
					
						
							|  |  |  | 		cache = { | 
					
						
							|  |  |  | 			version: k_cache_version, | 
					
						
							|  |  |  | 			about: {}, | 
					
						
							|  |  |  | 			last_row_id: 0, | 
					
						
							|  |  |  | 		}; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	let max_row_id = 0; | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  | 	await ssb.sqlAsync( | 
					
						
							|  |  |  | 		`
 | 
					
						
							| 
									
										
										
										
											2023-07-27 01:47:50 +00:00
										 |  |  | 			SELECT MAX(rowid) AS max_row_id FROM messages | 
					
						
							|  |  |  | 		`,
 | 
					
						
							|  |  |  | 		[], | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  | 		function (row) { | 
					
						
							| 
									
										
										
										
											2023-07-27 01:47:50 +00:00
										 |  |  | 			max_row_id = row.max_row_id; | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	); | 
					
						
							| 
									
										
										
										
											2023-07-27 01:47:50 +00:00
										 |  |  | 	for (let id of Object.keys(cache.about)) { | 
					
						
							|  |  |  | 		if (ids.indexOf(id) == -1) { | 
					
						
							|  |  |  | 			delete cache.about[id]; | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2022-08-04 00:57:56 +00:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-27 01:47:50 +00:00
										 |  |  | 	let abouts = []; | 
					
						
							|  |  |  | 	await ssb.sqlAsync( | 
					
						
							|  |  |  | 		`
 | 
					
						
							|  |  |  | 				SELECT | 
					
						
							|  |  |  | 					messages.* | 
					
						
							|  |  |  | 				FROM | 
					
						
							|  |  |  | 					messages, | 
					
						
							|  |  |  | 					json_each(?1) AS following | 
					
						
							|  |  |  | 				WHERE | 
					
						
							|  |  |  | 					messages.author = following.value AND | 
					
						
							|  |  |  | 					messages.rowid > ?3 AND | 
					
						
							|  |  |  | 					messages.rowid <= ?4 AND | 
					
						
							|  |  |  | 					json_extract(messages.content, '$.type') = 'about' | 
					
						
							|  |  |  | 				UNION | 
					
						
							|  |  |  | 				SELECT | 
					
						
							|  |  |  | 					messages.* | 
					
						
							|  |  |  | 				FROM | 
					
						
							|  |  |  | 					messages, | 
					
						
							|  |  |  | 					json_each(?2) AS following | 
					
						
							|  |  |  | 				WHERE | 
					
						
							|  |  |  | 					messages.author = following.value AND | 
					
						
							|  |  |  | 					messages.rowid <= ?4 AND | 
					
						
							|  |  |  | 					json_extract(messages.content, '$.type') = 'about' | 
					
						
							|  |  |  | 				ORDER BY messages.author, messages.sequence | 
					
						
							|  |  |  | 			`,
 | 
					
						
							|  |  |  | 		[ | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  | 			JSON.stringify(ids.filter((id) => cache.about[id])), | 
					
						
							|  |  |  | 			JSON.stringify(ids.filter((id) => !cache.about[id])), | 
					
						
							| 
									
										
										
										
											2023-07-27 01:47:50 +00:00
										 |  |  | 			cache.last_row_id, | 
					
						
							|  |  |  | 			max_row_id, | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  | 		] | 
					
						
							|  |  |  | 	); | 
					
						
							| 
									
										
										
										
											2023-07-27 01:47:50 +00:00
										 |  |  | 	for (let about of abouts) { | 
					
						
							|  |  |  | 		let content = JSON.parse(about.content); | 
					
						
							|  |  |  | 		if (content.about === about.author) { | 
					
						
							|  |  |  | 			delete content.type; | 
					
						
							|  |  |  | 			delete content.about; | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  | 			cache.about[about.author] = Object.assign( | 
					
						
							|  |  |  | 				cache.about[about.author] || {}, | 
					
						
							|  |  |  | 				content | 
					
						
							|  |  |  | 			); | 
					
						
							| 
									
										
										
										
											2023-07-27 01:47:50 +00:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2022-08-04 00:57:56 +00:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-07-27 01:47:50 +00:00
										 |  |  | 	cache.last_row_id = max_row_id; | 
					
						
							|  |  |  | 	await db.set('about', JSON.stringify(cache)); | 
					
						
							|  |  |  | 	users = users || {}; | 
					
						
							|  |  |  | 	for (let id of Object.keys(cache.about)) { | 
					
						
							|  |  |  | 		users[id] = Object.assign(users[id] || {}, cache.about[id]); | 
					
						
							| 
									
										
										
										
											2022-08-04 00:57:56 +00:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-07-27 01:47:50 +00:00
										 |  |  | 	return Object.assign({}, users); | 
					
						
							| 
									
										
										
										
											2022-08-04 00:57:56 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | async function getSize(db, id) { | 
					
						
							|  |  |  | 	let size = 0; | 
					
						
							| 
									
										
										
										
											2023-02-23 01:29:54 +00:00
										 |  |  | 	await ssb.sqlAsync( | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  | 		'SELECT (LENGTH(author) * COUNT(*) + SUM(LENGTH(content)) + SUM(LENGTH(id))) AS size FROM messages WHERE author = ?1', | 
					
						
							| 
									
										
										
										
											2022-08-04 00:57:56 +00:00
										 |  |  | 		[id], | 
					
						
							|  |  |  | 		function (row) { | 
					
						
							|  |  |  | 			size += row.size; | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	); | 
					
						
							| 
									
										
										
										
											2022-08-04 00:57:56 +00:00
										 |  |  | 	return size; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-27 01:47:50 +00:00
										 |  |  | async function getSizes(ids) { | 
					
						
							|  |  |  | 	let sizes = {}; | 
					
						
							|  |  |  | 	await ssb.sqlAsync( | 
					
						
							|  |  |  | 		`
 | 
					
						
							|  |  |  | 			SELECT | 
					
						
							|  |  |  | 				author, | 
					
						
							|  |  |  | 				(SUM(LENGTH(content)) + SUM(LENGTH(author)) + SUM(LENGTH(messages.id))) AS size | 
					
						
							|  |  |  | 			FROM messages | 
					
						
							|  |  |  | 			JOIN json_each(?) AS ids ON author = ids.value | 
					
						
							|  |  |  | 			GROUP BY author | 
					
						
							|  |  |  | 		`,
 | 
					
						
							|  |  |  | 		[JSON.stringify(ids)], | 
					
						
							|  |  |  | 		function (row) { | 
					
						
							|  |  |  | 			sizes[row.author] = row.size; | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	); | 
					
						
							| 
									
										
										
										
											2023-07-27 01:47:50 +00:00
										 |  |  | 	return sizes; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-04 00:57:56 +00:00
										 |  |  | function niceSize(bytes) { | 
					
						
							|  |  |  | 	let value = bytes; | 
					
						
							|  |  |  | 	let unit = 'B'; | 
					
						
							|  |  |  | 	const k_units = ['kB', 'MB', 'GB', 'TB']; | 
					
						
							|  |  |  | 	for (let u of k_units) { | 
					
						
							|  |  |  | 		if (value >= 1024) { | 
					
						
							|  |  |  | 			value /= 1024; | 
					
						
							|  |  |  | 			unit = u; | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return Math.round(value * 10) / 10 + ' ' + unit; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-27 01:47:50 +00:00
										 |  |  | function escape(value) { | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  | 	return value | 
					
						
							|  |  |  | 		.replaceAll('&', '&') | 
					
						
							|  |  |  | 		.replaceAll('<', '<') | 
					
						
							|  |  |  | 		.replaceAll('>', '>'); | 
					
						
							| 
									
										
										
										
											2022-08-04 00:57:56 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | async function main() { | 
					
						
							|  |  |  | 	await app.setDocument('<pre style="color: #fff">building...</pre>'); | 
					
						
							| 
									
										
										
										
											2023-07-27 01:47:50 +00:00
										 |  |  | 	let db = await database('ssb'); | 
					
						
							|  |  |  | 	let whoami = await ssb.getIdentities(); | 
					
						
							|  |  |  | 	let tree = ''; | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  | 	await app.setDocument( | 
					
						
							|  |  |  | 		`<pre style="color: #fff">Enumerating followed users...</pre>` | 
					
						
							|  |  |  | 	); | 
					
						
							| 
									
										
										
										
											2023-07-27 01:47:50 +00:00
										 |  |  | 	let following = await following_deep(whoami, 2, {}); | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  | 	await app.setDocument( | 
					
						
							|  |  |  | 		`<pre style="color: #fff">Getting names and sizes...</pre>` | 
					
						
							|  |  |  | 	); | 
					
						
							| 
									
										
										
										
											2023-07-27 01:47:50 +00:00
										 |  |  | 	let [about, sizes] = await Promise.all([ | 
					
						
							|  |  |  | 		fetch_about(db, following, {}), | 
					
						
							|  |  |  | 		getSizes(following), | 
					
						
							|  |  |  | 	]); | 
					
						
							|  |  |  | 	await app.setDocument(`<pre style="color: #fff">Finishing...</pre>`); | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  | 	following.sort((a, b) => (sizes[b] ?? 0) - (sizes[a] ?? 0)); | 
					
						
							| 
									
										
										
										
											2023-07-27 01:47:50 +00:00
										 |  |  | 	for (let id of following) { | 
					
						
							|  |  |  | 		tree += `<li><a href="/~core/ssb/#${id}">${escape(about[id]?.name ?? id)}</a> ${niceSize(sizes[id] ?? 0)}</li>\n`; | 
					
						
							| 
									
										
										
										
											2022-08-04 00:57:56 +00:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  | 	await app.setDocument( | 
					
						
							|  |  |  | 		'<!DOCTYPE html>\n<html>\n<body style="color: #fff"><h1>Following</h1>\n<ul>' + | 
					
						
							|  |  |  | 			tree + | 
					
						
							|  |  |  | 			'</ul>\n</body>\n</html>' | 
					
						
							|  |  |  | 	); | 
					
						
							| 
									
										
										
										
											2022-08-04 00:57:56 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-24 11:09:34 -05:00
										 |  |  | main(); |