import { createEnvelope, TYPES, PLATFORMS } from 'bridge-envelope'
// Strip HTML tags from Mastodon status content
function stripHtml (html) {
return html
.replace(/<br\s*\/?>/gi, '\n')
.replace(/<\/p>\s*<p>/gi, '\n\n')
.replace(/<[^>]+>/g, '')
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, "'")
.trim()
}
// Convert Mastodon status to bridge envelope
function statusToEnvelope (status) {
if (!status) return null
// Skip boosts — handle separately if needed
const actual = status.reblog || status
const author = actual.account?.acct || actual.account?.username || 'unknown'
const content = stripHtml(actual.content || '')
if (!content && !actual.media_attachments?.length) return null
let type = TYPES.TEXT
let mediaUrl = ''
let mediaType = ''
let replyTo = ''
// Media attachments
const attachment = actual.media_attachments?.[0]
if (attachment) {
type = TYPES.MEDIA
mediaUrl = attachment.url || attachment.preview_url || ''
mediaType = attachment.type || '' // image, video, gifv, audio
}
// Reply
if (actual.in_reply_to_id) {
replyTo = actual.in_reply_to_id
if (type === TYPES.TEXT) type = TYPES.REPLY
}
const envelope = createEnvelope({
type,
platform: PLATFORMS.MASTODON,
author,
content,
channel: status.reblog ? 'boost' : (actual.visibility || 'public'),
replyTo,
mediaUrl,
mediaType
})
// Preserve Mastodon status ID for dedup and threading
envelope._statusId = actual.id
envelope._spoilerText = actual.spoiler_text || ''
envelope._tags = (actual.tags || []).map(t => t.name)
envelope._isBoost = !!status.reblog
envelope._boostBy = status.reblog ? (status.account?.acct || '') : ''
return envelope
}
// Convert Keet message to Mastodon post text
function keetToMastodon (keetMsg, bridgeIdentity) {
if (!keetMsg) return null
// Skip own messages (bridge identity)
if (keetMsg.memberId === bridgeIdentity) return null
// Skip system messages
if (keetMsg.type === 'system') return null
const text = keetMsg.text || ''
if (!text.trim()) return null
// Skip messages already bridged from Mastodon
if (text.includes('[mastodon]') || text.includes('[Mastodon]')) return null
const name = keetMsg.name || keetMsg.username || 'someone'
return { author: name, text }
}
// Format envelope for Mastodon posting (from Keet to Mastodon)
function envelopeToStatusText (envelope) {
if (!envelope) return ''
const platformName = {
[PLATFORMS.KEET]: 'Keet',
[PLATFORMS.IRC]: 'IRC',
[PLATFORMS.DISCORD]: 'Discord',
[PLATFORMS.MATRIX]: 'Matrix',
[PLATFORMS.EMAIL]: 'Email'
}
const source = platformName[envelope.platform] || 'P2P'
return `[${source}] ${envelope.author}: ${envelope.content}`
}
// Format a Mastodon status for display in Keet
function statusToKeetText (envelope) {
if (!envelope) return ''
let text = ''
if (envelope._isBoost) {
text += `[mastodon] ${envelope._boostBy} boosted @${envelope.author}: `
} else {
text += `[mastodon] @${envelope.author}: `
}
if (envelope._spoilerText) {
text += `[CW: ${envelope._spoilerText}] `
}
text += envelope.content
if (envelope._tags?.length) {
const tags = envelope._tags.map(t => `#${t}`).join(' ')
if (!envelope.content.includes('#')) {
text += ` ${tags}`
}
}
if (envelope.mediaUrl) {
text += ` [${envelope.mediaType || 'media'}: ${envelope.mediaUrl}]`
}
return text
}
export {
statusToEnvelope,
keetToMastodon,
envelopeToStatusText,
statusToKeetText,
stripHtml
}