1
0
Fork 0
out-of-your-element-fork-th.../d2m/converters/user-to-mxid.js

84 lines
3 KiB
JavaScript
Raw Permalink Normal View History

2023-05-08 05:22:20 +00:00
// @ts-check
const assert = require("assert")
const passthrough = require("../../passthrough")
const {select} = passthrough
2023-05-08 05:22:20 +00:00
2023-11-25 09:26:26 +00:00
const SPECIAL_USER_MAPPINGS = new Map([
["1081004946872352958", ["clyde_ai", "clyde"]]
])
2023-05-08 05:22:20 +00:00
/**
* Downcased and stripped username. Can only include a basic set of characters.
* https://spec.matrix.org/v1.6/appendices/#user-identifiers
* @param {import("discord-api-types/v10").APIUser} user
* @returns {string} localpart
*/
function downcaseUsername(user) {
// First, try to convert the username to the set of allowed characters
let downcased = user.username.toLowerCase()
// spaces to underscores...
.replace(/ /g, "_")
// remove disallowed characters...
.replace(/[^a-z0-9._=/-]*/g, "")
// remove leading and trailing dashes and underscores...
.replace(/(?:^[_-]*|[_-]*$)/g, "")
// The new length must be at least 2 characters (in other words, it should have some content)
if (downcased.length < 2) {
downcased = user.id
}
return downcased
}
/** @param {string[]} preferences */
function* generateLocalpartAlternatives(preferences) {
const best = preferences[0]
2023-11-25 09:26:26 +00:00
assert(best)
2023-05-08 05:22:20 +00:00
// First, suggest the preferences...
for (const localpart of preferences) {
yield localpart
}
// ...then fall back to generating number suffixes...
let i = 2
while (true) {
yield best + (i++)
2023-08-24 05:23:32 +00:00
/* c8 ignore next */
2023-05-08 05:22:20 +00:00
}
}
/**
* Whole process for checking the database and generating the right sim name.
2023-05-09 03:29:46 +00:00
* It is very important this is not an async function: once the name has been chosen, the calling function should be able to immediately claim that name into the database in the same event loop tick.
2023-05-08 05:22:20 +00:00
* @param {import("discord-api-types/v10").APIUser} user
* @returns {string}
*/
function userToSimName(user) {
2023-11-25 09:26:26 +00:00
if (!SPECIAL_USER_MAPPINGS.has(user.id)) { // skip this check for known special users
assert.notEqual(user.discriminator, "0000", `cannot create user for a webhook: ${JSON.stringify(user)}`)
}
2023-05-08 05:22:20 +00:00
// 1. Is sim user already registered?
2023-10-05 23:31:10 +00:00
const existing = select("sim", "sim_name", {user_id: user.id}).pluck().get()
2024-03-04 04:02:38 +00:00
assert.equal(existing, null, "Shouldn't try to create a new name for an existing sim")
2023-05-08 05:22:20 +00:00
// 2. Register based on username (could be new or old format)
2023-11-25 09:26:26 +00:00
// (Unless it's a special user, in which case copy their provided mappings.)
2023-05-08 05:22:20 +00:00
const downcased = downcaseUsername(user)
2023-11-25 09:26:26 +00:00
const preferences = SPECIAL_USER_MAPPINGS.get(user.id) || [downcased]
2023-05-08 05:22:20 +00:00
if (user.discriminator.length === 4) { // Old style tag? If user.username is unavailable, try the full tag next
preferences.push(downcased + user.discriminator)
}
// Check for conflicts with already registered sims
2023-10-05 23:31:10 +00:00
const matches = select("sim", "sim_name", {}, "WHERE sim_name LIKE ? ESCAPE '@'").pluck().all(downcased + "%")
2023-05-08 05:22:20 +00:00
// Keep generating until we get a suggestion that doesn't conflict
for (const suggestion of generateLocalpartAlternatives(preferences)) {
if (!matches.includes(suggestion)) return suggestion
}
2023-08-24 05:23:32 +00:00
/* c8 ignore next */
2023-05-08 05:22:20 +00:00
throw new Error(`Ran out of suggestions when generating sim name. downcased: "${downcased}"`)
}
module.exports.userToSimName = userToSimName