/**
* bootstrap-rooms.mjs — One-shot metadata bootstrap for all rooms
*
* Sets topics, discoverable, language, category on all chat rooms.
* Skips DMs (RoomType=2). Then runs topic.announceAll.
*
* Requires: keet-daemon running on /tmp/keet-daemon.sock
* Usage: bare tests/bootstrap-rooms.mjs
*/
import Pipe from 'bare-pipe'
import FramedStream from 'framed-stream'
import b4a from 'b4a'
const SOCKET = '/tmp/keet-daemon.sock'
let rpcId = 0
// --- Room metadata assignments ---
// Key = room title, Value = { topics, category }
const ROOM_META = {
'AI Testing Town': { topics: ['ai', 'testing', 'p2p', 'development'], category: 'development' },
morky: { topics: ['testing', 'random'], category: 'social' },
'The Lobby': { topics: ['social', 'lobby', 'general'], category: 'social' },
invite_channel: { topics: ['invite', 'onboarding', 'welcome'], category: 'onboarding' },
'test 5': { topics: ['testing', 'p2p'], category: 'testing' },
'fifty seven': { topics: ['testing', 'p2p'], category: 'testing' },
'clode test room': { topics: ['ai', 'testing', 'bots'], category: 'testing' },
testy1: { topics: ['testing', 'p2p'], category: 'testing' },
newtron: { topics: ['testing', 'new'], category: 'testing' },
'test room 1': { topics: ['testing'], category: 'testing' },
'(untitled)': { topics: ['testing', 'scratch'], category: 'testing' },
'test 7': { topics: ['testing'], category: 'testing' }
}
// --- RPC client ---
function connect () {
const pipe = new Pipe(SOCKET)
const framed = new FramedStream(pipe)
const pending = new Map()
framed.on('data', (buf) => {
try {
const msg = JSON.parse(b4a.toString(buf))
const cb = pending.get(msg.id)
if (cb) {
pending.delete(msg.id)
if (msg.ok !== undefined && msg.data !== undefined) msg.result = msg.data
cb(msg)
}
} catch {}
})
function call (method, params = {}) {
return new Promise((resolve, reject) => {
const id = ++rpcId
const timeout = setTimeout(() => {
pending.delete(id)
reject(new Error('RPC timeout: ' + method))
}, 15000)
pending.set(id, (msg) => {
clearTimeout(timeout)
if (msg.error) reject(new Error(msg.error.message || msg.error))
else resolve(msg.result)
})
const payload = JSON.stringify({ jsonrpc: '2.0', id, method, params })
framed.write(b4a.from(payload))
})
}
function close () {
framed.destroy()
pipe.destroy()
}
return { call, close }
}
// --- Bootstrap ---
async function run () {
console.log('=== Room Metadata Bootstrap ===\n')
const client = connect()
const rooms = await client.call('rooms')
console.log('Found ' + rooms.length + ' rooms\n')
let bootstrapped = 0
let skipped = 0
for (const room of rooms) {
const roomId = room.roomId || room.id
const title = room.title || '(untitled)'
// Check room type — skip DMs
let roomType = 0
try {
const info = await client.call('room', { roomId })
roomType = info.digest?.settings?.roomType || 0
} catch {}
if (roomType === 2) {
console.log('SKIP (DM): ' + title)
skipped++
continue
}
// Look up metadata assignment
const assignment = ROOM_META[title]
if (!assignment) {
console.log('SKIP (no assignment): ' + title)
skipped++
continue
}
// Set metadata
try {
const result = await client.call('meta.set', {
roomId,
metadata: {
topics: assignment.topics,
discoverable: true,
language: 'en',
category: assignment.category
}
})
console.log('OK: ' + title + ' → topics=' + JSON.stringify(result.topics) + ', discoverable=' + result.discoverable)
bootstrapped++
} catch (err) {
console.log('FAIL: ' + title + ' → ' + err.message)
}
}
// Announce all discoverable rooms on DHT
console.log('\n--- DHT Announce ---')
try {
const result = await client.call('topic.announceAll')
console.log('OK: Announced ' + result.announced + ' rooms on DHT')
} catch (err) {
console.log('FAIL: topic.announceAll → ' + err.message)
}
// Verify — read back all metadata
console.log('\n--- Verification ---')
for (const room of rooms) {
const roomId = room.roomId || room.id
const title = room.title || '(untitled)'
try {
const meta = await client.call('meta.get', { roomId })
if (meta.discoverable) {
console.log(' ' + title + ': topics=' + JSON.stringify(meta.topics) + ' category=' + meta.category + ' lang=' + meta.language)
}
} catch {}
}
console.log('\n=== RESULT: ' + bootstrapped + ' bootstrapped, ' + skipped + ' skipped ===')
client.close()
setTimeout(() => Bare.exit(0), 500)
}
run().catch(err => {
console.log('FATAL: ' + err.message)
Bare.exit(1)
})