After install-bot-token and slash send-message, notify the Pear UI with structured sidecar messages for confirmation toasts and composer autofocus. Co-authored-by: Cursor <[email protected]>
1585 lines
45 KiB
JavaScript
1585 lines
45 KiB
JavaScript
'use strict'
|
|
|
|
const b4a = require('b4a')
|
|
const { createLogger } = require('pearcord-log')
|
|
const { USER_STATUS } = require('pearcord-platform')
|
|
|
|
const flowLog = createLogger('ui-flow')
|
|
|
|
let voiceCaptureChunkCount = 0
|
|
let voiceCaptureBytes = 0
|
|
let displayCaptureFrameCount = 0
|
|
let voiceCaptureTraceWindowStart = 0
|
|
let voiceCaptureTraceWindowBytes = 0
|
|
|
|
function resetVoiceCaptureTrace () {
|
|
voiceCaptureChunkCount = 0
|
|
voiceCaptureBytes = 0
|
|
voiceCaptureTraceWindowStart = Date.now()
|
|
voiceCaptureTraceWindowBytes = 0
|
|
}
|
|
|
|
function resetDisplayCaptureTrace () {
|
|
displayCaptureFrameCount = 0
|
|
}
|
|
|
|
function fromBase64 (str) {
|
|
if (typeof Buffer !== 'undefined') return Buffer.from(str || '', 'base64')
|
|
return b4a.from(str || '', 'base64')
|
|
}
|
|
|
|
function normalizeEmojiImage (image) {
|
|
if (!image?.dataBase64) return image
|
|
return {
|
|
data: fromBase64(image.dataBase64),
|
|
filename: image.filename,
|
|
mimeType: image.mimeType
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Dispatch one UI → sidecar IPC message (same contract as apps/pearcord/index.js).
|
|
* @param {import('pearcord-platform').PearcordPlatform} platform
|
|
* @param {object} msg parsed JSON line from pear-pipe
|
|
* @param {{ send?: (obj: object) => void, pushState?: () => void }} hooks
|
|
* @returns {Promise<boolean>} true when type was handled
|
|
*/
|
|
async function dispatchUiMessage (platform, msg, hooks = {}) {
|
|
const send = hooks.send || (() => {})
|
|
const pushState = hooks.pushState || (() => {})
|
|
const log = hooks.log || flowLog
|
|
const t = msg?.type
|
|
if (!t) {
|
|
log.warn('ipc missing type', { keys: msg ? Object.keys(msg) : [] })
|
|
return false
|
|
}
|
|
|
|
if (t === 'register') {
|
|
await platform.registerUser({
|
|
username: msg.username,
|
|
displayName: msg.displayName || msg.username
|
|
})
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'create-guild') {
|
|
await platform.createGuild({
|
|
name: msg.name || 'My Server',
|
|
publicListing: msg.publicListing !== false
|
|
})
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'join-invite') {
|
|
const result = await platform.joinInvite(msg.code)
|
|
pushState()
|
|
if (result?.kind === 'explore') {
|
|
send({ type: 'deep-link-result', url: msg.code, ...result })
|
|
}
|
|
return true
|
|
}
|
|
if (t === 'join-public-discovery') {
|
|
await platform.joinPublicDiscovery(msg.inviteCode)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'set-discovery-listing') {
|
|
await platform.setGuildDiscoveryListing(msg.guildId, msg.listed !== false)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'update-discovery-filter') {
|
|
await platform.setDiscoveryFilter({
|
|
query: msg.query,
|
|
sort: msg.sort,
|
|
minMembers: msg.minMembers,
|
|
tag: msg.tag
|
|
})
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'discovery-explore-opened') {
|
|
await platform.markDiscoveryExploreOpened()
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'mark-onboarding-explore-shown') {
|
|
await platform.markOnboardingExploreShown()
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'set-discovery-tags') {
|
|
await platform.setGuildDiscoveryTags(msg.guildId, msg.tags)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'set-automod-config') {
|
|
await platform.setAutomodConfig(msg.config || {})
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'upsert-automation-hook') {
|
|
await platform.upsertAutomationHook(msg.hook || {})
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'delete-automation-hook') {
|
|
await platform.deleteAutomationHook(msg.hookId)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'export-automation-manifest') {
|
|
const manifest = await platform.exportAutomationManifest()
|
|
send({ type: 'automation-manifest', manifest })
|
|
return true
|
|
}
|
|
if (t === 'import-automation-manifest') {
|
|
await platform.importAutomationManifest(msg.manifest || {}, {
|
|
merge: msg.merge !== false
|
|
})
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'set-automation-receipt-retention') {
|
|
await platform.setAutomationReceiptRetention(msg.retention || {})
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'set-hook-failure-prefs') {
|
|
await platform.setHookFailurePrefs(msg.prefs || {})
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'get-digest-relay-handoff') {
|
|
const handoff = await platform.getLastDigestRelayHandoff()
|
|
send({ type: 'digest-relay-handoff', handoff })
|
|
return true
|
|
}
|
|
if (t === 'dry-run-digest-webhook-relay') {
|
|
const out = await platform.dryRunDigestWebhookRelay({
|
|
webhookUrl: msg.webhookUrl
|
|
})
|
|
send({ type: 'digest-webhook-relay-dry-run', ...out })
|
|
return true
|
|
}
|
|
if (t === 'refresh-archive-peer-health') {
|
|
const out = await platform.refreshArchivePeerHealth()
|
|
pushState()
|
|
send({ type: 'archive-peer-health-refreshed', ...out })
|
|
return true
|
|
}
|
|
if (t === 'set-archive-health-prefs') {
|
|
await platform.setArchiveHealthPrefs(msg.prefs || {})
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'announce-worker-release') {
|
|
await platform.announceWorkerRelease({
|
|
workerVersion: msg.workerVersion,
|
|
pearUri: msg.pearUri,
|
|
releaseNotes: msg.releaseNotes,
|
|
channelTag: msg.channelTag,
|
|
allowDowngrade: !!msg.allowDowngrade
|
|
})
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'export-audit-log') {
|
|
const out = await platform.exportAuditLog({
|
|
format: msg.format,
|
|
filter: msg.filter,
|
|
limit: msg.limit,
|
|
gossip: msg.gossip !== false,
|
|
sign: msg.sign !== false && msg.format === 'csv',
|
|
recordMesh: msg.recordMesh !== false
|
|
})
|
|
if (msg.download && msg.format === 'csv') {
|
|
send({ type: 'audit-export-download', ...out })
|
|
} else {
|
|
send({ type: 'audit-export', ...out })
|
|
}
|
|
return true
|
|
}
|
|
if (t === 'sync-audit-export-csv') {
|
|
const out = await platform.pushAuditExportCsvToMesh({
|
|
filter: msg.filter,
|
|
limit: msg.limit
|
|
})
|
|
pushState()
|
|
send({ type: 'audit-export-csv-synced', ...out })
|
|
return true
|
|
}
|
|
if (t === 'export-hook-failure-digest') {
|
|
const out = await platform.exportHookFailureDigest({
|
|
format: msg.format,
|
|
windowMs: msg.windowMs
|
|
})
|
|
send({ type: 'hook-failure-digest-export', ...out })
|
|
return true
|
|
}
|
|
if (t === 'sync-hook-failure-digest') {
|
|
const digest = await platform.pushHookFailureDigestToMesh()
|
|
pushState()
|
|
send({ type: 'hook-failure-digest-synced', digest })
|
|
return true
|
|
}
|
|
if (t === 'set-audit-export-schedule') {
|
|
await platform.setAuditExportSchedule(msg.schedule || {})
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'run-audit-export-now') {
|
|
const out = await platform.runScheduledAuditExportNow()
|
|
pushState()
|
|
send({ type: 'audit-export-scheduled', result: out })
|
|
return true
|
|
}
|
|
if (t === 'set-digest-export-schedule') {
|
|
await platform.setDigestExportSchedule(msg.schedule || {})
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'run-digest-export-now') {
|
|
const digest = await platform.runScheduledDigestExportNow()
|
|
pushState()
|
|
send({ type: 'digest-export-scheduled', digest })
|
|
return true
|
|
}
|
|
if (t === 'get-worker-install-plan') {
|
|
const plan = await platform.fetchWorkerInstallPlan(msg.templateId || 'message-logger')
|
|
send({ type: 'worker-install-plan', plan })
|
|
return true
|
|
}
|
|
if (t === 'fetch-audit-export-archive') {
|
|
const fetchMesh = !!msg.fetchMesh
|
|
try {
|
|
const out = fetchMesh
|
|
? await platform.fetchAuditExportArchiveFromMesh(msg.archiveId, {
|
|
format: msg.format,
|
|
timeoutMs: msg.timeoutMs,
|
|
maxAttempts: msg.maxAttempts,
|
|
baseDelayMs: msg.baseDelayMs,
|
|
targetMemberId: msg.targetMemberId || null
|
|
})
|
|
: await platform.exportAuditExportArchive(msg.archiveId, {
|
|
format: msg.format
|
|
})
|
|
send({ type: 'audit-export-archive-body', ...out, fetchMesh })
|
|
} catch (err) {
|
|
send({
|
|
type: 'audit-export-archive-error',
|
|
archiveId: msg.archiveId,
|
|
message: err?.message || String(err),
|
|
fetchAttempts: err?.fetchAttempts || 0,
|
|
fetchMesh
|
|
})
|
|
}
|
|
return true
|
|
}
|
|
if (t === 'get-automation-schedule-dashboard') {
|
|
const dashboard = await platform.getAutomationScheduleDashboard()
|
|
send({ type: 'automation-schedule-dashboard', dashboard })
|
|
return true
|
|
}
|
|
if (t === 'get-automation-health-dashboard') {
|
|
const dashboard = await platform.getAutomationHealthDashboard()
|
|
send({ type: 'automation-health-dashboard', dashboard })
|
|
return true
|
|
}
|
|
if (t === 'set-automation-digest-notify-prefs') {
|
|
await platform.setAutomationDigestNotifyPrefs(msg.prefs || {})
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'set-hook-failure-digest-notify-prefs') {
|
|
await platform.setHookFailureDigestNotifyPrefs(msg.prefs || {})
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'ensure-mod-digest-group-dm') {
|
|
const channel = await platform.ensureModDigestGroupDmChannel()
|
|
pushState()
|
|
send({ type: 'mod-digest-group-dm-ready', channelId: channel?.id || null })
|
|
return true
|
|
}
|
|
if (t === 'run-overdue-automation-schedules') {
|
|
const result = await platform.runOverdueAutomationSchedules()
|
|
pushState()
|
|
send({ type: 'overdue-automation-ran', result })
|
|
return true
|
|
}
|
|
if (t === 'list-audit-archive-fetch-peers') {
|
|
const peers = await platform.listAuditArchiveFetchPeers()
|
|
send({ type: 'audit-archive-fetch-peers', peers })
|
|
return true
|
|
}
|
|
if (t === 'fetch-automation-digest-snapshot') {
|
|
const fetchMesh = true
|
|
try {
|
|
const out = await platform.fetchAutomationDigestSnapshotFromMesh(msg.snapshotId, {
|
|
timeoutMs: msg.timeoutMs,
|
|
maxAttempts: msg.maxAttempts,
|
|
baseDelayMs: msg.baseDelayMs,
|
|
targetMemberId: msg.targetMemberId || null
|
|
})
|
|
send({ type: 'automation-digest-snapshot-body', ...out, fetchMesh })
|
|
} catch (err) {
|
|
send({
|
|
type: 'automation-digest-snapshot-error',
|
|
snapshotId: msg.snapshotId,
|
|
message: err?.message || String(err),
|
|
fetchAttempts: err?.fetchAttempts || 0,
|
|
fetchMesh
|
|
})
|
|
}
|
|
return true
|
|
}
|
|
if (t === 'export-automation-schedule-digest') {
|
|
const out = await platform.exportAutomationScheduleDigest({
|
|
format: msg.format,
|
|
windowMs: msg.windowMs
|
|
})
|
|
send({ type: 'automation-schedule-digest-export', ...out })
|
|
return true
|
|
}
|
|
if (t === 'run-worker-trial-matrix') {
|
|
const result = await platform.runIntegrationWorkerTrialMatrix({
|
|
holdMs: msg.holdMs
|
|
})
|
|
send({ type: 'worker-trial-matrix', result })
|
|
return true
|
|
}
|
|
if (t === 'set-automation-digest-export-schedule') {
|
|
await platform.setAutomationDigestExportSchedule(msg.schedule || {})
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'run-automation-digest-export-now') {
|
|
const out = await platform.runScheduledAutomationDigestExportNow()
|
|
pushState()
|
|
send({ type: 'automation-digest-export-scheduled', result: out })
|
|
return true
|
|
}
|
|
if (t === 'run-worker-install-trial') {
|
|
const result = await platform.runIntegrationWorkerTrial({
|
|
templateId: msg.templateId,
|
|
holdMs: msg.holdMs
|
|
})
|
|
send({ type: 'worker-install-trial', result })
|
|
return true
|
|
}
|
|
if (t === 'export-integration-worker-script') {
|
|
const script = platform.buildIntegrationWorkerScript(msg.templateId || 'message-logger')
|
|
send({
|
|
type: 'integration-worker-script',
|
|
templateId: msg.templateId,
|
|
script
|
|
})
|
|
return true
|
|
}
|
|
if (t === 'dry-run-automation-hook') {
|
|
const out = await platform.dryRunAutomationHook(msg.hookId, msg.eventType)
|
|
send({ type: 'automation-hook-dry-run', ...out })
|
|
return true
|
|
}
|
|
if (t === 'dry-run-automod') {
|
|
const out = await platform.dryRunAutomodMessage(msg.content || '', {
|
|
memberRoles: ['member']
|
|
})
|
|
send({ type: 'automod-dry-run', ...out })
|
|
return true
|
|
}
|
|
if (t === 'set-announcement-crosspost') {
|
|
await platform.setAnnouncementCrosspostTargets(
|
|
msg.channelId,
|
|
msg.targetChannelIds || []
|
|
)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'set-announcement-hub-publish') {
|
|
await platform.setAnnouncementHubPublisher(msg.channelId, !!msg.enabled)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'subscribe-remote-announcement-hub') {
|
|
await platform.subscribeRemoteAnnouncementHub({
|
|
sourceGuildId: msg.sourceGuildId,
|
|
sourceChannelId: msg.sourceChannelId,
|
|
targetChannelId: msg.targetChannelId
|
|
})
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'follow-announcement') {
|
|
await platform.followAnnouncementChannel(msg.channelId)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'unfollow-announcement') {
|
|
await platform.unfollowAnnouncementChannel(msg.channelId)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'add-guild-boost') {
|
|
await platform.addGuildBoost(msg.amount ?? 1)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'contribute-guild-boost') {
|
|
await platform.contributeGuildBoost(msg.amount ?? 1)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'set-guild-boost-banner') {
|
|
await platform.setGuildBoostBannerColor(msg.bannerColor)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'voice-capture-chunk') {
|
|
voiceCaptureChunkCount++
|
|
const chunk = msg.data
|
|
let nbytes = 0
|
|
if (chunk) {
|
|
if (typeof chunk === 'string') nbytes = chunk.length
|
|
else if (chunk.byteLength != null) nbytes = chunk.byteLength
|
|
else if (chunk.length != null) nbytes = chunk.length
|
|
voiceCaptureBytes += nbytes
|
|
}
|
|
if (process.env.PEARCORD_LOG_VOICE_CAPTURE_TRACE === '1') {
|
|
if (!voiceCaptureTraceWindowStart) voiceCaptureTraceWindowStart = Date.now()
|
|
voiceCaptureTraceWindowBytes += nbytes
|
|
if (voiceCaptureChunkCount % 200 === 0) {
|
|
const elapsedSec = Math.max(
|
|
0.001,
|
|
(Date.now() - voiceCaptureTraceWindowStart) / 1000
|
|
)
|
|
log.trace('voice-capture chunks', {
|
|
count: voiceCaptureChunkCount,
|
|
bytes: voiceCaptureBytes,
|
|
bytesPerSec: Math.round(voiceCaptureTraceWindowBytes / elapsedSec)
|
|
})
|
|
voiceCaptureTraceWindowStart = Date.now()
|
|
voiceCaptureTraceWindowBytes = 0
|
|
}
|
|
}
|
|
platform.ingestVoiceCapture(msg.data)
|
|
return true
|
|
}
|
|
if (t === 'display-capture-frame') {
|
|
displayCaptureFrameCount++
|
|
if (
|
|
process.env.PEARCORD_LOG_VOICE_CAPTURE_TRACE === '1' &&
|
|
displayCaptureFrameCount % 200 === 0
|
|
) {
|
|
log.trace('display-capture frames', { count: displayCaptureFrameCount })
|
|
}
|
|
platform.ingestDisplayCapture({
|
|
data: msg.data,
|
|
mimeType: msg.mimeType,
|
|
width: msg.width,
|
|
height: msg.height
|
|
})
|
|
return true
|
|
}
|
|
if (t === 'create-invite') {
|
|
const inv = await platform.createInvite()
|
|
send({ type: 'invite', invite: inv })
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'select-guild') {
|
|
log.info('select-guild', { guildId: msg.guildId })
|
|
await platform.loadGuild(msg.guildId)
|
|
if (platform.guildOpenNoViewableChannels) {
|
|
send({
|
|
type: 'log',
|
|
record: {
|
|
ts: new Date().toISOString(),
|
|
level: 'warn',
|
|
scope: 'platform',
|
|
msg: 'guild open: no viewable channels',
|
|
meta: {
|
|
guildId: msg.guildId,
|
|
hint: 'You may lack channel view permission in this server'
|
|
}
|
|
}
|
|
})
|
|
}
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'join-stage') {
|
|
try {
|
|
await platform.joinStageChannel(msg.channelId)
|
|
pushState()
|
|
} catch (err) {
|
|
const errMsg = err?.message || String(err)
|
|
if (/voice channel is full/i.test(errMsg)) {
|
|
send({ type: 'error', message: errMsg })
|
|
return true
|
|
}
|
|
if (/no permission to connect/i.test(errMsg)) {
|
|
send({ type: 'error', message: errMsg })
|
|
return true
|
|
}
|
|
throw err
|
|
}
|
|
return true
|
|
}
|
|
if (t === 'request-stage-speak') {
|
|
try {
|
|
await platform.requestStageSpeak()
|
|
pushState()
|
|
} catch (err) {
|
|
if (/voice channel is full/i.test(err?.message || '')) {
|
|
send({ type: 'error', message: err.message })
|
|
return true
|
|
}
|
|
throw err
|
|
}
|
|
return true
|
|
}
|
|
if (t === 'select-channel') {
|
|
await platform.selectChannel(msg.channelId)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'mark-channel-read') {
|
|
const guildId = platform.guild?.guild?.id
|
|
if (guildId && msg.channelId) {
|
|
await platform.markChannelRead(msg.channelId, guildId)
|
|
pushState()
|
|
let dismissChannelId = msg.channelId
|
|
const channels = (await platform.guild?.listChannels?.()) || []
|
|
const ch = channels.find((c) => c.id === msg.channelId)
|
|
if (ch?.type === 'thread' && ch.parentId) dismissChannelId = ch.parentId
|
|
send({
|
|
type: 'mark-channel-read-cleared',
|
|
guildId,
|
|
channelId: dismissChannelId,
|
|
threadChannelId: ch?.type === 'thread' ? msg.channelId : undefined
|
|
})
|
|
}
|
|
return true
|
|
}
|
|
if (t === 'join-voice') {
|
|
try {
|
|
await platform.joinVoiceChannel(msg.channelId)
|
|
pushState()
|
|
} catch (err) {
|
|
const errMsg = err?.message || String(err)
|
|
if (/voice channel is full/i.test(errMsg)) {
|
|
send({ type: 'error', message: errMsg })
|
|
return true
|
|
}
|
|
if (/no permission to connect|cannot join voice listen-only/i.test(errMsg)) {
|
|
send({ type: 'error', message: errMsg })
|
|
return true
|
|
}
|
|
throw err
|
|
}
|
|
return true
|
|
}
|
|
if (t === 'join-voice-listen') {
|
|
try {
|
|
await platform.joinVoiceListenOnly(msg.channelId)
|
|
pushState()
|
|
} catch (err) {
|
|
const errMsg = err?.message || String(err)
|
|
if (/voice channel is full/i.test(errMsg)) {
|
|
send({ type: 'error', message: errMsg })
|
|
return true
|
|
}
|
|
if (/no permission to connect|cannot join voice listen-only/i.test(errMsg)) {
|
|
send({ type: 'error', message: errMsg })
|
|
return true
|
|
}
|
|
throw err
|
|
}
|
|
return true
|
|
}
|
|
if (t === 'leave-voice') {
|
|
const payload = await platform.leaveVoiceChannel()
|
|
resetVoiceCaptureTrace()
|
|
pushState()
|
|
if (!payload) {
|
|
log.debug('leave-voice noop', { reason: 'not in voice channel' })
|
|
}
|
|
return true
|
|
}
|
|
if (t === 'start-screen-share') {
|
|
resetDisplayCaptureTrace()
|
|
await platform.startScreenShare(msg.label)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'stop-screen-share') {
|
|
await platform.stopScreenShare()
|
|
resetDisplayCaptureTrace()
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'set-voice-mute') {
|
|
try {
|
|
await platform.setVoiceMute({ muted: msg.muted, deafened: msg.deafened })
|
|
pushState()
|
|
} catch (err) {
|
|
const msgText = err?.message || String(err)
|
|
if (msgText === 'not in voice' || /listen-only voice/i.test(msgText)) {
|
|
log.debug('set-voice-mute noop', {
|
|
muted: msg.muted,
|
|
deafened: msg.deafened,
|
|
reason: msgText
|
|
})
|
|
return true
|
|
}
|
|
throw err
|
|
}
|
|
return true
|
|
}
|
|
if (t === 'register-slash-commands') {
|
|
await platform.registerSlashCommands(msg.commands || [])
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'delete-slash-command') {
|
|
await platform.deleteSlashCommand(msg.name)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'create-bot-install-token') {
|
|
const out = await platform.createBotInstallToken({
|
|
name: msg.name,
|
|
intents: msg.intents,
|
|
permissions: msg.permissions,
|
|
botPublicKey: msg.botPublicKey,
|
|
ttlMs: msg.ttlMs
|
|
})
|
|
send({ type: 'bot-install-token', token: out.token, bot: out.bot, expiresAt: out.expiresAt })
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'install-bot-token') {
|
|
const out = await platform.installBotFromToken(msg.token)
|
|
send({
|
|
type: 'bot-installed',
|
|
botName: out?.bot?.name || null,
|
|
bot: out?.bot || null
|
|
})
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'send-message') {
|
|
const trimmed = String(msg.content || '').trim()
|
|
const slashMatch = trimmed.match(/^\/([a-z0-9_-]+)/i)
|
|
await platform.sendMessage(msg.content, {
|
|
replyToId: msg.replyToId,
|
|
attachmentIds: msg.attachmentIds,
|
|
stickerNames: msg.stickerNames
|
|
})
|
|
if (slashMatch) send({ type: 'slash-invoked', name: slashMatch[1] })
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'stage-attachment') {
|
|
const att = await platform.stageAttachment({
|
|
data: fromBase64(msg.dataBase64),
|
|
filename: msg.filename,
|
|
mimeType: msg.mimeType
|
|
})
|
|
send({
|
|
type: 'attachment-staged',
|
|
attachment: att,
|
|
uploadLocalId: msg.uploadLocalId || null
|
|
})
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'update-guild-settings') {
|
|
await platform.updateGuildSettings({
|
|
name: msg.name,
|
|
iconHash: msg.iconHash,
|
|
nsfwGateEnabled: msg.nsfwGateEnabled
|
|
})
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'set-guild-icon-image') {
|
|
await platform.setGuildIconImage({
|
|
data: fromBase64(msg.dataBase64),
|
|
filename: msg.filename,
|
|
mimeType: msg.mimeType
|
|
})
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'clear-guild-icon') {
|
|
await platform.updateGuildSettings({ iconHash: null })
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'request-guild-icon') {
|
|
const preview = await platform.readGuildIconPreview(msg.iconHash, {
|
|
guildId: msg.guildId
|
|
})
|
|
send({ type: 'guild-icon-image', preview, guildId: msg.guildId || null })
|
|
return true
|
|
}
|
|
if (t === 'jump-to-message') {
|
|
const result = await platform.jumpToMessage(msg.messageId)
|
|
pushState()
|
|
send({ type: 'jump-to-message-result', ...result })
|
|
return true
|
|
}
|
|
if (t === 'navigate-deep-link') {
|
|
const result = await platform.navigateDeepLink(msg.url)
|
|
pushState()
|
|
send({ type: 'deep-link-result', url: msg.url, ...result })
|
|
return true
|
|
}
|
|
if (t === 'format-message-deep-link') {
|
|
const url = platform.formatMessageDeepLink(msg.messageId, {
|
|
channelId: msg.channelId,
|
|
guildId: msg.guildId
|
|
})
|
|
send({ type: 'message-deep-link', url, messageId: msg.messageId })
|
|
return true
|
|
}
|
|
if (t === 'format-invite-deep-link') {
|
|
const code = msg.inviteCode || msg.shareCode || msg.code
|
|
const url = platform.formatInviteDeepLink(code)
|
|
send({ type: 'invite-deep-link', url, inviteCode: code })
|
|
return true
|
|
}
|
|
if (t === 'format-discovery-baseline-link') {
|
|
const url = platform.formatDiscoveryExploreBaselineDeepLink()
|
|
send({ type: 'discovery-baseline-deep-link', url })
|
|
return true
|
|
}
|
|
if (t === 'format-guild-deep-link') {
|
|
const url = platform.formatGuildDeepLink({
|
|
guildId: msg.guildId,
|
|
channelId: msg.channelId
|
|
})
|
|
send({ type: 'guild-deep-link', url, guildId: msg.guildId, channelId: msg.channelId || null })
|
|
return true
|
|
}
|
|
if (t === 'format-settings-deep-link') {
|
|
const url = platform.formatSettingsDeepLink({
|
|
section: msg.section,
|
|
scope: msg.scope,
|
|
guildId: msg.guildId
|
|
})
|
|
send({
|
|
type: 'settings-deep-link',
|
|
url,
|
|
section: msg.section || null,
|
|
scope: msg.scope || 'user',
|
|
guildId: msg.guildId || null
|
|
})
|
|
return true
|
|
}
|
|
if (t === 'mark-notification-read') {
|
|
const row = await platform.markNotificationRead(msg.notificationId)
|
|
pushState()
|
|
if (row?.channelId) {
|
|
let dismissChannelId = row.channelId
|
|
const channels = (await platform.guild?.listChannels?.()) || []
|
|
const ch = channels.find((c) => c.id === row.channelId)
|
|
if (ch?.type === 'thread' && ch.parentId) dismissChannelId = ch.parentId
|
|
send({
|
|
type: 'mark-notification-read-cleared',
|
|
guildId: row.guildId || platform.guild?.guild?.id || null,
|
|
channelId: dismissChannelId,
|
|
notificationChannelId: row.channelId
|
|
})
|
|
}
|
|
return true
|
|
}
|
|
if (t === 'open-notification') {
|
|
if (msg.notificationId) {
|
|
await platform.markNotificationRead(msg.notificationId)
|
|
}
|
|
const result = await platform.openNotificationTarget({
|
|
guildId: msg.guildId,
|
|
channelId: msg.channelId,
|
|
messageId: msg.messageId
|
|
})
|
|
pushState()
|
|
const focusChannelId = result?.channelId || msg.channelId || platform.activeChannelId
|
|
if (focusChannelId) {
|
|
send({ type: 'composer-focus-channel', channelId: focusChannelId })
|
|
}
|
|
send({
|
|
type: 'open-notification-opened',
|
|
guildId: msg.guildId || platform.guild?.guild?.id || null,
|
|
channelId: msg.channelId || result.channelId || null,
|
|
messageId: msg.messageId || null
|
|
})
|
|
send({ type: 'open-notification-result', ...result })
|
|
return true
|
|
}
|
|
if (t === 'format-device-pair-deep-link') {
|
|
const url = platform.formatDevicePairDeepLink(msg.code || msg.pairCode)
|
|
send({ type: 'device-pair-deep-link', url, code: msg.code || msg.pairCode })
|
|
return true
|
|
}
|
|
if (t === 'set-presence') {
|
|
const custom = msg.customStatus !== undefined ? msg.customStatus : undefined
|
|
const activity = msg.activity !== undefined ? msg.activity : undefined
|
|
platform.setPresence(msg.status || USER_STATUS.ONLINE, custom, activity)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'set-activity') {
|
|
platform.setActivity(msg.activity ?? null)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'clear-activity') {
|
|
platform.setActivity(null)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'set-activity-image') {
|
|
await platform.setActivityImageFromBytes({
|
|
data: fromBase64(msg.dataBase64),
|
|
filename: msg.filename,
|
|
mimeType: msg.mimeType,
|
|
slot: msg.slot || 'large'
|
|
})
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'request-activity-image') {
|
|
const preview = await platform.readActivityImagePreview(
|
|
msg.attachmentId,
|
|
msg.slot || 'large'
|
|
)
|
|
send({ type: 'activity-image', preview, slot: msg.slot || 'large' })
|
|
return true
|
|
}
|
|
if (t === 'edit-message') {
|
|
await platform.editMessage(msg.messageId, msg.content)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'delete-message') {
|
|
await platform.deleteMessage(msg.messageId)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'create-thread') {
|
|
const thread = await platform.createThread({ messageId: msg.messageId, name: msg.name })
|
|
pushState()
|
|
send({ type: 'composer-focus-channel', channelId: thread.id })
|
|
return true
|
|
}
|
|
if (t === 'create-forum-post') {
|
|
const result = await platform.createForumPost({
|
|
title: msg.title,
|
|
content: msg.content,
|
|
tags: msg.tags
|
|
})
|
|
pushState()
|
|
const threadId = result?.thread?.id
|
|
if (threadId) send({ type: 'composer-focus-channel', channelId: threadId })
|
|
return true
|
|
}
|
|
if (t === 'set-forum-channel-palette') {
|
|
await platform.setForumChannelPalette(msg.channelId, msg.tags)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'update-forum-tag-filter') {
|
|
platform.setForumTagFilter(msg.tag)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'set-forum-include-archived') {
|
|
platform.setForumIncludeArchived(msg.include)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'set-threads-panel-archived') {
|
|
await platform.setThreadsPanelIncludeArchived(!!msg.includeArchived)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'set-threads-panel-filter') {
|
|
await platform.setThreadsPanelFilter(msg.filter)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'set-voice-roster-muted-only') {
|
|
await platform.setVoiceRosterMutedOnly(!!msg.mutedOnly)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'set-voice-roster-deafened-only') {
|
|
await platform.setVoiceRosterDeafenedOnly(!!msg.deafenedOnly)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'set-voice-roster-expanded') {
|
|
await platform.setVoiceRosterChannelExpanded(msg.channelId, !!msg.expanded)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'set-voice-roster-combined-filter') {
|
|
await platform.setVoiceRosterCombinedFilter(!!msg.enabled)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'set-threads-panel-parent-filter') {
|
|
platform.setThreadsPanelParentFilter(msg.parentChannelId || null)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'set-threads-panel-sort') {
|
|
platform.setThreadsPanelSort(msg.panelSort || 'activity')
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'toggle-thread-pin') {
|
|
await platform.toggleThreadPinned(msg.threadId)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'set-thread-notification-muted') {
|
|
await platform.setThreadNotificationMuted(msg.threadId, msg.muted !== false)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'set-channel-notification-level') {
|
|
await platform.setChannelNotificationLevel(msg.channelId, msg.level || 'all')
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'set-channel-mute-duration') {
|
|
await platform.setChannelMuteDuration(msg.channelId, msg.durationMs ?? 0)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'archive-thread') {
|
|
await platform.archiveThread(msg.threadId, msg.archived !== false)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'create-category') {
|
|
await platform.createCategory({ name: msg.name })
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'create-channel') {
|
|
const channel = await platform.createChannel({
|
|
name: msg.name,
|
|
type: msg.channelType,
|
|
parentId: msg.parentId
|
|
})
|
|
await platform.selectChannel(channel.id)
|
|
send({ type: 'composer-focus-channel', channelId: channel.id })
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'reorder-channels') {
|
|
await platform.reorderChannelsInParent({
|
|
parentId: msg.parentId ?? null,
|
|
channelIds: msg.channelIds || []
|
|
})
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'move-channel-to-parent') {
|
|
await platform.moveChannelToParent({
|
|
channelId: msg.channelId,
|
|
parentId: msg.parentId ?? null
|
|
})
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'rename-channel') {
|
|
await platform.renameChannel({ channelId: msg.channelId, name: msg.name })
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'delete-channel') {
|
|
await platform.deleteChannel(msg.channelId)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'set-channel-user-limit') {
|
|
await platform.setChannelUserLimit(msg.channelId, msg.userLimit)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'set-channel-nsfw') {
|
|
await platform.setChannelNsfw(msg.channelId, msg.nsfw)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'reorder-categories') {
|
|
await platform.reorderCategories({ categoryIds: msg.categoryIds || [] })
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'toggle-reaction') {
|
|
await platform.toggleReaction(msg.messageId, msg.emoji)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'select-home') {
|
|
await platform.openHome()
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'send-friend-request') {
|
|
await platform.sendFriendRequest({
|
|
peerUserId: msg.peerUserId,
|
|
peerDisplayName: msg.peerDisplayName
|
|
})
|
|
pushState()
|
|
send({ type: 'show-toast', message: 'Friend request sent on the contacts mesh' })
|
|
return true
|
|
}
|
|
if (t === 'accept-friend-request') {
|
|
await platform.acceptFriendRequest(msg.peerUserId)
|
|
pushState()
|
|
send({ type: 'show-toast', message: 'Friend request accepted' })
|
|
return true
|
|
}
|
|
if (t === 'decline-friend-request') {
|
|
await platform.declineFriendRequest(msg.peerUserId)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'remove-contact') {
|
|
await platform.removeContact(msg.peerUserId)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'block-contact') {
|
|
await platform.blockContact(msg.peerUserId)
|
|
pushState()
|
|
send({ type: 'show-toast', message: 'User blocked — they cannot send you friend requests' })
|
|
return true
|
|
}
|
|
if (t === 'unblock-contact') {
|
|
await platform.unblockContact(msg.peerUserId)
|
|
pushState()
|
|
send({ type: 'show-toast', message: 'User unblocked' })
|
|
return true
|
|
}
|
|
if (t === 'request-attachment-preview') {
|
|
const preview = await platform.readAttachmentPreview(msg.attachmentId)
|
|
send({
|
|
type: 'attachment-preview',
|
|
preview,
|
|
fetchSource: preview?.fetchSource || null
|
|
})
|
|
return true
|
|
}
|
|
if (t === 'upsert-guild-emoji') {
|
|
await platform.upsertGuildEmoji({
|
|
name: msg.name,
|
|
glyph: msg.glyph,
|
|
description: msg.description,
|
|
attachmentId: msg.attachmentId,
|
|
packName: msg.packName,
|
|
image: normalizeEmojiImage(msg.image)
|
|
})
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'set-guild-emoji-roles') {
|
|
await platform.setGuildEmojiAllowedRoles({
|
|
name: msg.name,
|
|
allowedRoles: msg.allowedRoles
|
|
})
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'upsert-emoji-pack') {
|
|
await platform.upsertEmojiPack({
|
|
name: msg.name,
|
|
displayName: msg.displayName,
|
|
description: msg.description,
|
|
sortOrder: msg.sortOrder,
|
|
coverEmojiName: msg.coverEmojiName,
|
|
emojiNames: msg.emojiNames
|
|
})
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'remove-emoji-pack') {
|
|
await platform.removeEmojiPack(msg.name)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'remove-guild-emoji') {
|
|
await platform.removeGuildEmoji(msg.name)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'upsert-guild-sticker') {
|
|
await platform.upsertGuildSticker({
|
|
name: msg.name,
|
|
description: msg.description,
|
|
attachmentId: msg.attachmentId,
|
|
packName: msg.packName,
|
|
image: normalizeEmojiImage(msg.image)
|
|
})
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'upsert-sticker-pack') {
|
|
await platform.upsertStickerPack({
|
|
name: msg.name,
|
|
displayName: msg.displayName,
|
|
description: msg.description,
|
|
sortOrder: msg.sortOrder,
|
|
coverStickerName: msg.coverStickerName,
|
|
stickerNames: msg.stickerNames
|
|
})
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'remove-sticker-pack') {
|
|
await platform.removeStickerPack(msg.name)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'remove-guild-sticker') {
|
|
await platform.removeGuildSticker(msg.name)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'upsert-guild-sound') {
|
|
await platform.upsertGuildSound({
|
|
name: msg.name,
|
|
description: msg.description,
|
|
attachmentId: msg.attachmentId,
|
|
volume: msg.volume,
|
|
audio: normalizeEmojiImage(msg.audio)
|
|
})
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'remove-guild-sound') {
|
|
await platform.removeGuildSound(msg.name)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'play-soundboard') {
|
|
await platform.playSoundboardSound(msg.name)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'request-sound-preview') {
|
|
const preview = await platform.readSoundPreview(msg.name || msg.attachmentId)
|
|
send({ type: 'sound-preview', preview, soundName: msg.name || null })
|
|
return true
|
|
}
|
|
if (t === 'request-emoji-image') {
|
|
const attachmentId = msg.attachmentId || null
|
|
const emojiName = msg.name || null
|
|
try {
|
|
const preview = await platform.readEmojiImage(emojiName || attachmentId)
|
|
if (preview) {
|
|
send({ type: 'emoji-image', preview, emojiName })
|
|
} else {
|
|
flowLog.debug('emoji image miss', { attachmentId, emojiName })
|
|
send({
|
|
type: 'log',
|
|
record: {
|
|
ts: new Date().toISOString(),
|
|
level: 'debug',
|
|
scope: 'emoji',
|
|
msg: 'image miss',
|
|
meta: { attachmentId, emojiName }
|
|
}
|
|
})
|
|
send({ type: 'emoji-image-miss', attachmentId, emojiName })
|
|
}
|
|
} catch (err) {
|
|
flowLog.warn('emoji image request failed', { attachmentId, emojiName, err })
|
|
send({
|
|
type: 'emoji-image-miss',
|
|
attachmentId,
|
|
emojiName,
|
|
message: err?.message || String(err)
|
|
})
|
|
}
|
|
return true
|
|
}
|
|
if (t === 'stage-guild-emoji-image') {
|
|
const att = await platform.stageGuildEmojiImage({
|
|
data: fromBase64(msg.dataBase64),
|
|
filename: msg.filename,
|
|
mimeType: msg.mimeType
|
|
})
|
|
send({ type: 'emoji-image-staged', attachment: att })
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'update-search') {
|
|
platform.setSearch({
|
|
query: msg.query,
|
|
scope: msg.scope,
|
|
includeMesh: msg.includeMesh
|
|
})
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'remove-search-recent') {
|
|
await platform.removeSearchRecent(msg.query)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'refresh-search') {
|
|
await platform.refreshSearch()
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'open-dm') {
|
|
await platform.openDM({
|
|
peerUserId: msg.peerUserId,
|
|
peerDisplayName: msg.peerDisplayName || msg.peerUserId?.slice(0, 8)
|
|
})
|
|
pushState()
|
|
if (platform.activeChannelId) {
|
|
send({ type: 'composer-focus-channel', channelId: platform.activeChannelId })
|
|
}
|
|
return true
|
|
}
|
|
if (t === 'create-group-dm') {
|
|
await platform.createGroupDM({
|
|
peerUserIds: msg.peerUserIds || [],
|
|
name: msg.name
|
|
})
|
|
pushState()
|
|
if (platform.activeChannelId) {
|
|
send({ type: 'composer-focus-channel', channelId: platform.activeChannelId })
|
|
}
|
|
return true
|
|
}
|
|
if (t === 'open-group-dm') {
|
|
await platform.openGroupDM({ channelId: msg.channelId })
|
|
pushState()
|
|
if (platform.activeChannelId) {
|
|
send({ type: 'composer-focus-channel', channelId: platform.activeChannelId })
|
|
}
|
|
return true
|
|
}
|
|
if (t === 'typing-start' || t === 'typing') {
|
|
log.debug('typing.ipc', {
|
|
channelId: platform.activeChannelId,
|
|
mode: platform.mode
|
|
})
|
|
platform.signalTyping()
|
|
return true
|
|
}
|
|
if (t === 'update-profile') {
|
|
await platform.updateProfile({ displayName: msg.displayName })
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'update-profile-cosmetics') {
|
|
await platform.updateProfileCosmetics({
|
|
bannerColor: msg.bannerColor,
|
|
avatarRing: msg.avatarRing,
|
|
accentColor: msg.accentColor,
|
|
profileBio: msg.profileBio
|
|
})
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'set-profile-banner-image') {
|
|
await platform.setProfileBannerImage({
|
|
data: fromBase64(msg.dataBase64),
|
|
filename: msg.filename,
|
|
mimeType: msg.mimeType
|
|
})
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'request-profile-banner-image') {
|
|
const preview = await platform.readProfileBannerPreview(msg.attachmentId)
|
|
send({ type: 'profile-banner-image', preview })
|
|
return true
|
|
}
|
|
if (t === 'request-peer-profile-banner-image') {
|
|
const preview = await platform.readPeerProfileBannerPreview(msg.userId)
|
|
send({ type: 'profile-banner-image', preview })
|
|
return true
|
|
}
|
|
if (t === 'pin-message') {
|
|
await platform.pinMessage(msg.messageId)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'unpin-message') {
|
|
await platform.unpinMessage(msg.messageId)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'ban-member') {
|
|
await platform.banMember({ userId: msg.userId, reason: msg.reason })
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'kick-member') {
|
|
await platform.kickMember({ userId: msg.userId, reason: msg.reason })
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'timeout-member') {
|
|
await platform.timeoutMember({
|
|
userId: msg.userId,
|
|
durationMinutes: msg.durationMinutes,
|
|
reason: msg.reason
|
|
})
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'set-channel-topic') {
|
|
await platform.setChannelTopic(msg.channelId, msg.topic)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'set-channel-description') {
|
|
await platform.setChannelDescription(msg.channelId, msg.description)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'upsert-channel-overwrite') {
|
|
await platform.upsertChannelPermissionOverwrite(msg.channelId, {
|
|
targetType: msg.targetType,
|
|
targetId: msg.targetId,
|
|
allowView: msg.allowView,
|
|
denyView: msg.denyView
|
|
})
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'delete-channel-overwrite') {
|
|
await platform.deleteChannelPermissionOverwrite(
|
|
msg.channelId,
|
|
msg.targetType,
|
|
msg.targetId
|
|
)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'set-slowmode') {
|
|
await platform.setChannelSlowmode(msg.channelId, msg.slowmodeSeconds)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'set-member-role') {
|
|
await platform.setMemberRole(msg.userId, msg.role)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'create-guild-custom-role') {
|
|
await platform.createGuildCustomRole({
|
|
name: msg.name,
|
|
color: msg.color,
|
|
permissions: msg.permissions
|
|
})
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'update-guild-custom-role') {
|
|
await platform.updateGuildCustomRole(msg.roleId, {
|
|
name: msg.name,
|
|
color: msg.color,
|
|
permissions: msg.permissions,
|
|
hoist: msg.hoist
|
|
})
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'delete-guild-custom-role') {
|
|
await platform.deleteGuildCustomRole(msg.roleId)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'reorder-guild-custom-roles') {
|
|
await platform.reorderGuildCustomRoles(msg.roleIds || [])
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'set-member-custom-roles') {
|
|
await platform.setMemberCustomRoles(msg.userId, msg.roleIds || [])
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'update-notification-prefs') {
|
|
await platform.updateNotificationPrefs(msg.prefs || {})
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'update-user-prefs') {
|
|
await platform.updateUserPrefs(msg.prefs || {})
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'create-channel-webhook') {
|
|
const out = await platform.createChannelWebhook({
|
|
channelId: msg.channelId,
|
|
name: msg.name,
|
|
avatarUrl: msg.avatarUrl
|
|
})
|
|
pushState()
|
|
send({ type: 'channel-webhook-created', ...out })
|
|
return true
|
|
}
|
|
if (t === 'delete-channel-webhook') {
|
|
await platform.deleteChannelWebhook(msg.webhookId)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'execute-channel-webhook') {
|
|
const msgRow = await platform.executeChannelWebhook({
|
|
token: msg.token,
|
|
content: msg.content,
|
|
username: msg.username
|
|
})
|
|
pushState()
|
|
send({ type: 'channel-webhook-executed', messageId: msgRow?.id })
|
|
return true
|
|
}
|
|
if (t === 'set-audit-log-export-filter') {
|
|
const filter = await platform.setAuditLogExportFilter(msg.filter || 'all')
|
|
pushState()
|
|
send({ type: 'audit-log-export-filter', filter })
|
|
return true
|
|
}
|
|
if (t === 'create-server-folder') {
|
|
const folder = await platform.createServerFolder({ name: msg.name, color: msg.color })
|
|
if (msg.initialGuildId && folder?.id) {
|
|
await platform.assignGuildToFolder(msg.initialGuildId, folder.id)
|
|
}
|
|
if (msg.mergeGuildId && folder?.id) {
|
|
await platform.assignGuildToFolder(msg.mergeGuildId, folder.id)
|
|
}
|
|
pushState()
|
|
send({
|
|
type: 'server-folder-created',
|
|
folder,
|
|
initialGuildId: msg.initialGuildId || null
|
|
})
|
|
return true
|
|
}
|
|
if (t === 'delete-server-folder') {
|
|
await platform.deleteServerFolder(msg.folderId)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'assign-guild-to-folder') {
|
|
await platform.assignGuildToFolder(msg.guildId, msg.folderId || null)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'set-server-folder-collapsed') {
|
|
await platform.setServerFolderCollapsed(msg.folderId, !!msg.collapsed)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'set-server-folder-color') {
|
|
await platform.setServerFolderColor(msg.folderId, msg.color)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'rename-server-folder') {
|
|
await platform.renameServerFolder(msg.folderId, msg.name)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'reorder-server-folders') {
|
|
await platform.reorderServerFolders(msg.folderIds || [])
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'set-compose-draft') {
|
|
flowLog.trace('compose draft ipc', {
|
|
channelId: msg.channelId,
|
|
len: String(msg.text ?? '').length
|
|
})
|
|
await platform.setComposeDraft(msg.channelId, msg.text ?? '')
|
|
return true
|
|
}
|
|
if (t === 'export-diagnostics-jsonl') {
|
|
const out = platform.writeDiagnosticsJsonl(msg.lines || [], msg.meta || {})
|
|
send({ type: 'diagnostics-jsonl-exported', ...out })
|
|
return true
|
|
}
|
|
if (t === 'mark-all-notifications-read') {
|
|
await platform.markAllNotificationsRead()
|
|
pushState()
|
|
send({ type: 'mark-all-notifications-read-cleared' })
|
|
if (platform.activeChannelId) {
|
|
send({ type: 'composer-focus-channel', channelId: platform.activeChannelId })
|
|
}
|
|
return true
|
|
}
|
|
if (t === 'refresh-notifications') {
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'create-device-pair-code') {
|
|
const invite = await platform.createDevicePairCode({
|
|
deviceLabel: msg.deviceLabel,
|
|
ttlMs: msg.ttlMs
|
|
})
|
|
const pairUrl = platform.formatDevicePairDeepLink(invite?.code)
|
|
let qrSvg = null
|
|
if (pairUrl) {
|
|
try {
|
|
const { renderQrSvg } = require('pearcord-qr')
|
|
qrSvg = renderQrSvg(pairUrl, { size: 180 })
|
|
} catch {
|
|
qrSvg = null
|
|
}
|
|
}
|
|
send({ type: 'device-pair-code', invite, pairUrl, qrSvg })
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'link-device-pair-code') {
|
|
await platform.linkDevicePairCode(msg.code, { deviceLabel: msg.deviceLabel })
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'set-step-up-pin') {
|
|
await platform.setStepUpPin(msg.pin)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'verify-step-up-pin') {
|
|
await platform.verifyStepUpPin(msg.pin)
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'clear-step-up-pin') {
|
|
await platform.clearStepUpPin()
|
|
pushState()
|
|
return true
|
|
}
|
|
if (t === 'refresh') {
|
|
pushState()
|
|
return true
|
|
}
|
|
|
|
log.warn('unhandled ipc', { type: t })
|
|
return false
|
|
}
|
|
|
|
module.exports = {
|
|
dispatchUiMessage,
|
|
fromBase64,
|
|
normalizeEmojiImage
|
|
}
|