Gather Town Guest Management

I use this script to manage who has access to join my Gather.town space, so I don't have to manually approve folks joining, they don't need me to be there to join, and they can only access it if they're logged in and are on the list (they've purchased a ticket).

Very cool thing I can throw together to solve my problems in an hour.

Find the most up-to-date version in my repo: https://github.com/kentcdodds/.kenv/blob/main/scripts/gather-guest.ts

Open gather-guest in Script Kit

// Menu: Gather Guest List
// Description: Handle the Guest List for Gather
// Author: Kent C. Dodds
// Twitter: @kentcdodds
import '@johnlindquist/kit'
import {z} from 'zod'
const GuestObjectSchema = z.object({
name: z.string().optional(),
affiliation: z.string().optional(),
role: z.string().optional(),
})
const GuestsSchema = z.record(z.string().email(), GuestObjectSchema)
const GATHER_API_KEY = await env('GATHER_API_KEY', async () => {
return await arg(
{
placeholder: 'GATHER_API_KEY',
ignoreBlur: true,
},
() =>
md(`
# Get a Gather API Key
[app.gather.town/apikeys](https://app.gather.town/apikeys)
`),
)
})
const GATHER_SPACE_ID = await env('GATHER_SPACE_ID', async () => {
return await arg(
{
placeholder: 'GATHER_SPACE_ID',
ignoreBlur: true,
},
() =>
md(`
# Specify the Gather Space ID
It's everything after "app/" in this URL with "/" replaced by "\\":
https://app.gather.town/app/BL0B93FK23T/example
`),
)
})
async function go() {
const params = new URLSearchParams({
apiKey: GATHER_API_KEY,
spaceId: GATHER_SPACE_ID,
})
const rawGuests = await fetch(
`https://gather.town/api/getEmailGuestlist?${params}`,
).then(r => r.json())
const guests = GuestsSchema.parse(rawGuests)
const choices = [
{name: '➕ Add a guest', value: {type: 'add-guest'}},
...Object.entries(guests).map(([email, {name, affiliation, role}]) => ({
name: `${email} (${name?.trim() || 'Unnamed'}, ${
affiliation?.trim() || 'Unaffiliated'
}, ${role?.trim() || 'No role'})`,
value: {type: 'modify-guest', email},
})),
]
const rawSelection = await arg(
{placeholder: 'Which guest would you like to modify?'},
choices,
)
const SelectionSchema = z.union([
z.object({
type: z.literal('add-guest'),
}),
z.object({
type: z.literal('modify-guest'),
email: z.string(),
}),
])
const selection = SelectionSchema.parse(rawSelection)
switch (selection.type) {
case 'add-guest': {
await addGuest()
return go()
}
case 'modify-guest': {
await modifyGuest(selection.email, guests)
return go()
}
}
}
async function addGuest() {
const email = z
.string()
.email()
.parse(await arg({placeholder: `What's the guests' email?`}))
const body = {
apiKey: GATHER_API_KEY,
spaceId: GATHER_SPACE_ID,
guestlist: {[email]: {}},
}
const updateResponse = await fetch(
'https://api.gather.town/api/setEmailGuestlist',
{
method: 'POST',
body: JSON.stringify(body),
headers: {
'content-type': 'application/json',
},
},
)
const update = await updateResponse.json()
console.log('Guest Update: ', update[email])
}
async function modifyGuest(
email: string,
guests: z.infer<typeof GuestsSchema>,
) {
const guest = guests[email]
const action = await arg({placeholder: `What would you like to do?`}, [
{name: 'Remove Guest', value: 'remove'},
{name: `Change Guest Email (${email})`, value: 'change-email'},
{
name: `Change Guest Name (${guest.name?.trim() || 'Unnamed'})`,
value: 'change-name',
},
{
name: `Change Guest Affiliation (${
guest.affiliation?.trim() || 'Unaffiliated'
})`,
value: 'change-affiliation',
},
{
name: `Change Guest Role (${guest.role?.trim() || 'No role'})`,
value: 'change-role',
},
{
name: `Cancel`,
value: 'cancel',
},
])
switch (action) {
case 'remove': {
delete guests[email]
break
}
case 'change-email': {
const newEmail = z
.string()
.email()
.parse(await arg({placeholder: 'New Email'}))
guests[newEmail] = guests[email]
delete guests[email]
email = newEmail
break
}
case 'change-name': {
const newName = await arg({placeholder: 'New Name'})
if (newName) {
guests[email].name = newName
} else {
delete guests[email].name
}
break
}
case 'change-affiliation': {
const newAffiliation = await arg({
placeholder: 'New Affiliation',
})
if (newAffiliation) {
guests[email].affiliation = newAffiliation
} else {
delete guests[email].affiliation
}
break
}
case 'change-role': {
const newRole = await arg({placeholder: 'New Role'})
if (newRole) {
guests[email].role = newRole
} else {
delete guests[email].role
}
break
}
case 'cancel': {
return go()
}
}
const body = {
apiKey: GATHER_API_KEY,
spaceId: GATHER_SPACE_ID,
guestlist: guests,
overwrite: true,
}
const updateResponse = await fetch(
'https://api.gather.town/api/setEmailGuestlist',
{
method: 'POST',
body: JSON.stringify(body),
headers: {
'content-type': 'application/json',
},
},
)
const update = await updateResponse.json()
console.log('Guest Update: ', update[email])
}
go()