From 983ea0cb52162493e3b11b571e07bb7bd27fff77 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Sat, 18 Jul 2026 12:06:03 +1200 Subject: [PATCH] m->d typing --- src/m2d/actions/typing.js | 41 +++++++++++++++++++++++++++++++++ src/m2d/event-dispatcher.js | 16 +++++++++++++ src/matrix/read-registration.js | 5 ++++ src/types.d.ts | 10 ++++++++ 4 files changed, 72 insertions(+) create mode 100644 src/m2d/actions/typing.js diff --git a/src/m2d/actions/typing.js b/src/m2d/actions/typing.js new file mode 100644 index 0000000..88ae87b --- /dev/null +++ b/src/m2d/actions/typing.js @@ -0,0 +1,41 @@ +// @ts-check + +const {discord} = require("../../passthrough") + +/** @type {Set} channel ids that are currently typing */ +const isTyping = new Set() +/** @type {Map} channel id -> interval */ +const intervals = new Map() + +/** @param {string} channelID */ +function startTyping(channelID) { + if (!isTyping.has(channelID)) { + // Start a new typing session + isTyping.add(channelID) + intervals.set(channelID, setInterval(refreshTyping, 8e3, channelID)) + discord.snow.channel.startChannelTyping(channelID).catch(() => {}) + } +} + +/** @param {string} channelID */ +function refreshTyping(channelID) { + if (isTyping.has(channelID)) { + // Continue typing session + discord.snow.channel.startChannelTyping(channelID).catch(() => {}) + } else { + // End typing session + clearInterval(intervals.get(channelID)) + intervals.delete(channelID) + } +} + +/** @param {string} channelID */ +function stopTyping(channelID) { + if (isTyping.has(channelID)) { + // Schedule typing session to end + isTyping.delete(channelID) + } +} + +module.exports.startTyping = startTyping +module.exports.stopTyping = stopTyping diff --git a/src/m2d/event-dispatcher.js b/src/m2d/event-dispatcher.js index 0ca36a6..7dfe102 100644 --- a/src/m2d/event-dispatcher.js +++ b/src/m2d/event-dispatcher.js @@ -36,6 +36,8 @@ const roomUpgrade = require("../matrix/room-upgrade") const retrigger = sync.require("../d2m/actions/retrigger") /** @type {import("../matrix/homeserver-status")} */ const homeserverStatus = sync.require("../matrix/homeserver-status") +/** @type {import("./actions/typing")} */ +const typing = sync.require("./actions/typing") const {reg} = require("../matrix/read-registration") let lastReportedEvent = 0 @@ -527,6 +529,20 @@ async event => { await createRoom.unbridgeChannel(channel, channel["guild_id"], "Encrypted rooms are not supported. This room was removed from the bridge.") })) +sync.addTemporaryListener(as, "ephemeral_type:m.typing", guard("m.typing", +/** + * @param {Ty.Event.Outer_M_Typing} event + */ +async event => { + const channelID = select("channel_room", "channel_id", {room_id: event.room_id}).pluck().get() + if (!channelID) return + if (event.content.user_ids.length) { + typing.startTyping(channelID) + } else { + typing.stopTyping(channelID) + } +})) + module.exports.stringifyErrorStack = stringifyErrorStack module.exports.cleanErrorStack = cleanErrorStack module.exports.sendError = sendError diff --git a/src/matrix/read-registration.js b/src/matrix/read-registration.js index d1243a7..e6ca53f 100644 --- a/src/matrix/read-registration.js +++ b/src/matrix/read-registration.js @@ -17,6 +17,10 @@ function checkRegistration(reg) { assert(reg.ooye?.server_origin.match(/^https?:\/\//), "server origin must start with http or https") assert.notEqual(reg.ooye?.server_origin.slice(-1), "/", "server origin must not end in slash") assert.match(reg.url, /^https?:/, "url must start with http:// or https://") + if (!reg.receive_ephemeral) { + reg.receive_ephemeral = true + writeRegistration(reg) + } } /* c8 ignore next 4 */ @@ -50,6 +54,7 @@ function getTemplateRegistration(serverName) { ], sender_localpart: `${namespace_prefix}bot`, rate_limited: false, + receive_ephemeral: true, socket: 6693, ooye: { namespace_prefix, diff --git a/src/types.d.ts b/src/types.d.ts index be037ca..856ab1c 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -18,6 +18,7 @@ export type AppServiceRegistrationConfig = { } protocols: [string] rate_limited: boolean + receive_ephemeral: boolean socket?: string | number, ooye: { namespace_prefix: string @@ -59,6 +60,7 @@ export type InitialAppServiceRegistrationConfig = { } protocols: [string] rate_limited: boolean + receive_ephemeral: boolean socket?: string | number, ooye: { namespace_prefix: string @@ -396,6 +398,14 @@ export namespace Event { rotation_period_ms?: number rotation_period_msgs?: number } + + export type Outer_M_Typing = { + content: { + user_ids: string[] + } + room_id: string + type: "m.typing" + } } export namespace R {