Compare commits

..

3 commits

8 changed files with 77 additions and 18 deletions

View file

@ -12,6 +12,8 @@ const file = sync.require("../../matrix/file")
const api = sync.require("../../matrix/api")
/** @type {import("../../matrix/kstate")} */
const ks = sync.require("../../matrix/kstate")
/** @type {import("../../discord/utils")} */
const utils = sync.require("../../discord/utils")
/** @type {import("./create-space")}) */
const createSpace = sync.require("./create-space") // watch out for the require loop
@ -119,6 +121,9 @@ async function channelToKState(channel, guild) {
join_rules = {join_rule: PRIVACY_ENUMS.ROOM_JOIN_RULES[privacyLevel]}
}
const everyonePermissions = utils.getPermissions([], guild.roles, undefined, channel.permission_overwrites)
const everyoneCanMentionEveryone = utils.hasAllPermissions(everyonePermissions, ["MentionEveryone"])
const channelKState = {
"m.room.name/": {name: convertedName},
"m.room.topic/": {topic: convertedTopic},
@ -136,7 +141,7 @@ async function channelToKState(channel, guild) {
"m.room.avatar": 0
},
notifications: {
room: 20 // TODO: Matrix users should have the same abilities as unprivileged Discord members. So make this automatically configured based on the guild or channel's default mention everyone permissions. That way if unprivileged Discord members can mention everyone, Matrix users can too.
room: everyoneCanMentionEveryone ? 0 : 20
},
users: reg.ooye.invite.reduce((a, c) => (a[c] = 100, a), {})
},

View file

@ -1,5 +1,6 @@
// @ts-check
const mixin = require("mixin-deep")
const {channelToKState, _convertNameAndTopic} = require("./create-room")
const {kstateStripConditionals} = require("../../matrix/kstate")
const {test} = require("supertape")
@ -39,6 +40,16 @@ test("channel2room: invite-only privacy room", async t => {
)
})
test("channel2room: room where limited people can mention everyone", async t => {
const limitedGuild = mixin({}, testData.guild.general)
limitedGuild.roles[0].permissions = (BigInt(limitedGuild.roles[0].permissions) - 131072n).toString()
const limitedRoom = mixin({}, testData.room.general, {"m.room.power_levels/": {notifications: {room: 20}}})
t.deepEqual(
kstateStripConditionals(await channelToKState(testData.channel.general, limitedGuild).then(x => x.channelKState)),
limitedRoom
)
})
test("convertNameAndTopic: custom name and topic", t => {
t.deepEqual(
_convertNameAndTopic({id: "123", name: "the-twilight-zone", topic: "Spooky stuff here. :ghost:", type: 0}, {id: "456"}, "hauntings"),

View file

@ -1,6 +1,6 @@
// @ts-check
const assert = require("assert")
const assert = require("assert").strict
const reg = require("../../matrix/read-registration")
const DiscordTypes = require("discord-api-types/v10")
const mixin = require("mixin-deep")
@ -180,11 +180,12 @@ function _hashProfileContent(content, powerLevel) {
* 5. If the state content or power level have changed, send them to Matrix and update them in the database for next time
* @param {DiscordTypes.APIUser} user
* @param {Omit<DiscordTypes.APIGuildMember, "user">} member
* @param {DiscordTypes.APIGuild} guild
* @param {DiscordTypes.APIGuildChannel} channel
* @param {DiscordTypes.APIGuild} guild
* @param {string} roomID
* @returns {Promise<string>} mxid of the updated sim
*/
async function syncUser(user, member, guild, channel, roomID) {
async function syncUser(user, member, channel, guild, roomID) {
const mxid = await ensureSimJoined(user, roomID)
const content = await memberToStateContent(user, member, guild.id)
const powerLevel = memberToPowerLevel(user, member, guild, channel)
@ -204,6 +205,9 @@ async function syncUser(user, member, guild, channel, roomID) {
return mxid
}
/**
* @param {string} roomID
*/
async function syncAllUsersInRoom(roomID) {
const mxids = select("sim_member", "mxid", {room_id: roomID}).pluck().all()
@ -228,7 +232,7 @@ async function syncAllUsersInRoom(roomID) {
assert.ok(user)
console.log(`[user sync] to matrix: ${user.username} in ${channel.name}`)
await syncUser(user, member, guild, channel, roomID)
await syncUser(user, member, channel, guild, roomID)
}
}

View file

@ -1,6 +1,7 @@
// @ts-check
const assert = require("assert")
const assert = require("assert").strict
const DiscordTypes = require("discord-api-types/v10")
const passthrough = require("../../passthrough")
const { discord, sync, db } = passthrough
@ -18,17 +19,18 @@ const createRoom = sync.require("../actions/create-room")
const dUtils = sync.require("../../discord/utils")
/**
* @param {import("discord-api-types/v10").GatewayMessageCreateDispatchData} message
* @param {import("discord-api-types/v10").APIGuild} guild
* @param {DiscordTypes.GatewayMessageCreateDispatchData} message
* @param {DiscordTypes.APIGuildChannel} channel
* @param {DiscordTypes.APIGuild} guild
* @param {{speedbump_id: string, speedbump_webhook_id: string} | null} row data about the webhook which is proxying messages in this channel
*/
async function sendMessage(message, guild, row) {
async function sendMessage(message, channel, guild, row) {
const roomID = await createRoom.ensureRoom(message.channel_id)
let senderMxid = null
if (!dUtils.isWebhookMessage(message)) {
if (message.member) { // available on a gateway message create event
senderMxid = await registerUser.syncUser(message.author, message.member, message.guild_id, roomID)
senderMxid = await registerUser.syncUser(message.author, message.member, channel, guild, roomID)
} else { // well, good enough...
senderMxid = await registerUser.ensureSimJoined(message.author, roomID)
}

View file

@ -248,7 +248,7 @@ module.exports = {
if (affected) return
// @ts-ignore
await sendMessage.sendMessage(message, guild, row),
await sendMessage.sendMessage(message, channel, guild, row),
await discordCommandHandler.execute(message, channel, guild)
},

View file

@ -26,7 +26,7 @@ function getPermissions(userRoles, guildRoles, userID, channelOverwrites) {
}
if (channelOverwrites) {
/** @type {((overwrite: Required<DiscordTypes.APIGuildChannel>["permission_overwrites"][0]) => any)[]} */
/** @type {((overwrite: Required<DiscordTypes.APIOverwrite>) => any)[]} */
const actions = [
// Channel @everyone deny
overwrite => overwrite.id === everyoneID && (allowed &= ~BigInt(overwrite.deny)),
@ -63,7 +63,7 @@ function getPermissions(userRoles, guildRoles, userID, channelOverwrites) {
*/
function hasPermission(resolvedPermissions, permissionToCheckFor) {
// Make sure permissionToCheckFor has exactly one permission in it
assert.equal(permissionToCheckFor.toString(2).match(/1/g), 1)
assert.equal(permissionToCheckFor.toString(2).match(/1/g)?.length, 1)
// Do the actual calculation
return (resolvedPermissions & permissionToCheckFor) === permissionToCheckFor
}

View file

@ -1,3 +1,4 @@
const DiscordTypes = require("discord-api-types/v10")
const {test} = require("supertape")
const data = require("../test/data")
const utils = require("./utils")
@ -82,3 +83,27 @@ test("getPermissions: channel overwrite to allow role works", t => {
const want = BigInt(1 << 10 | 1 << 16)
t.equal((permissions & want), want)
})
test("hasSomePermissions: detects the permission", t => {
const userPermissions = DiscordTypes.PermissionFlagsBits.MentionEveryone | DiscordTypes.PermissionFlagsBits.BanMembers
const canRemoveMembers = utils.hasSomePermissions(userPermissions, ["KickMembers", "BanMembers"])
t.equal(canRemoveMembers, true)
})
test("hasSomePermissions: doesn't detect not the permission", t => {
const userPermissions = DiscordTypes.PermissionFlagsBits.MentionEveryone | DiscordTypes.PermissionFlagsBits.SendMessages
const canRemoveMembers = utils.hasSomePermissions(userPermissions, ["KickMembers", "BanMembers"])
t.equal(canRemoveMembers, false)
})
test("hasAllPermissions: detects the permissions", t => {
const userPermissions = DiscordTypes.PermissionFlagsBits.KickMembers | DiscordTypes.PermissionFlagsBits.BanMembers | DiscordTypes.PermissionFlagsBits.MentionEveryone
const canRemoveMembers = utils.hasAllPermissions(userPermissions, ["KickMembers", "BanMembers"])
t.equal(canRemoveMembers, true)
})
test("hasAllPermissions: doesn't detect not the permissions", t => {
const userPermissions = DiscordTypes.PermissionFlagsBits.MentionEveryone | DiscordTypes.PermissionFlagsBits.SendMessages | DiscordTypes.PermissionFlagsBits.KickMembers
const canRemoveMembers = utils.hasAllPermissions(userPermissions, ["KickMembers", "BanMembers"])
t.equal(canRemoveMembers, false)
})

View file

@ -47,6 +47,9 @@ module.exports = {
},
users: {
"@test_auto_invite:example.org": 100
},
notifications: {
room: 0
}
},
"chat.schildi.hide_ui/read_receipts": {hidden: true},
@ -98,7 +101,6 @@ module.exports = {
icon: "a_f83622e09ead74f0c5c527fe241f8f8c",
emojis: [
{
version: 0,
roles: [],
require_colons: true,
name: "hippo",
@ -108,7 +110,6 @@ module.exports = {
animated: false
},
{
version: 0,
roles: [],
require_colons: true,
name: "hipposcope",
@ -121,7 +122,20 @@ module.exports = {
premium_subscription_count: 14,
roles: [
{
version: 1696964862461,
unicode_emoji: null,
tags: {},
position: 0,
permissions: '559623605575360',
name: '@everyone',
mentionable: false,
managed: false,
id: '112760669178241024',
icon: null,
hoist: false,
flags: 0,
color: 0
},
{
unicode_emoji: null,
tags: {},
position: 22,
@ -135,7 +149,6 @@ module.exports = {
flags: 0,
color: 0
}, {
version: 1696964862776,
unicode_emoji: null,
tags: {},
position: 131,
@ -149,7 +162,6 @@ module.exports = {
flags: 0,
color: 11076095
}, {
version: 1696964862698,
unicode_emoji: '🍂',
tags: {},
position: 102,