OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-social / bridge-mastodon / lib / bridge.js

87 lines · javascript ref:
import { MastodonClient } from './mastodon-client.js'
import { KeetClient } from './keet-client.js'
import { Router } from './router.js'
import { statusToEnvelope, keetToMastodon, statusToKeetText, envelopeToStatusText } from './adapter.js'

class MastodonBridge {
  constructor (opts = {}) {
    this.mastodon = new MastodonClient({
      instance: opts.instance,
      token: opts.token
    })
    this.keet = new KeetClient({
      host: opts.keetHost,
      port: opts.keetPort
    })
    this.roomId = opts.roomId
    this.visibility = opts.visibility || 'unlisted' // default post visibility
    this.bridgeIdentity = opts.bridgeIdentity || null
    this.tag = opts.tag || null // optional hashtag filter
    this.onLog = opts.onLog || (() => {})

    this.router = new Router({
      onKeetMessage: (author, text) => {
        this.keet.send(this.roomId, text).catch(err => {
          this.onLog(`Keet send error: ${err.message}`)
        })
      },
      onExternalMessage: (author, text) => {
        this.mastodon.postStatus(text, { visibility: this.visibility }).catch(err => {
          this.onLog(`Mastodon post error: ${err.message}`)
        })
      }
    })
  }

  async start () {
    // Get bridge identity from keet-daemon
    if (!this.bridgeIdentity) {
      try {
        const identity = await this.keet.identity()
        this.bridgeIdentity = identity.memberId || identity.id || ''
        this.onLog(`Bridge identity: ${this.bridgeIdentity}`)
      } catch (err) {
        this.onLog(`Warning: Could not get identity: ${err.message}`)
      }
    }

    // Verify Mastodon credentials
    try {
      const account = await this.mastodon.verifyCredentials()
      this.onLog(`Mastodon: logged in as @${account.acct || account.username}`)
    } catch (err) {
      this.onLog(`Mastodon auth failed: ${err.message}`)
      throw err
    }

    // Stream Mastodon → Keet
    const streamType = this.tag ? `hashtag&tag=${this.tag}` : 'user'
    this.mastodon.stream(streamType, (status) => {
      const envelope = statusToEnvelope(status)
      if (!envelope) return

      const text = statusToKeetText(envelope)
      this.router.routeToKeet(envelope.author, text)
    })

    // Watch Keet → Mastodon
    this.keet.watch(this.roomId, (msg) => {
      const result = keetToMastodon(msg, this.bridgeIdentity)
      if (!result) return

      const text = `${result.author}: ${result.text}`
      this.router.routeToExternal(result.author, text)
    })

    this.onLog(`Bridge started: Mastodon (${this.mastodon.instance}) ↔ Keet`)
  }

  stop () {
    this.mastodon.stopStream()
    this.keet.stopWatch()
    this.onLog('Bridge stopped')
  }
}

export { MastodonBridge }