Initial commit

This commit is contained in:
payacom
2026-07-07 13:23:55 +03:30
parent a7e4f68495
commit 2c6d8a4434
2 changed files with 50 additions and 23 deletions

View File

@@ -12,28 +12,46 @@ const isUsernameTaken = async (UserModel, userName, excludeUserId) => {
return true
}
const buildCandidates = (base, attempt) => {
const random2 = () => String(Math.floor(Math.random() * 90 + 10))
const random3 = () => String(Math.floor(Math.random() * 900 + 100))
const random4 = () => String(Math.floor(Math.random() * 9000 + 1000))
const splitBaseAndSuffix = (value) => {
const match = value.match(/^(.*?)(\d+)$/)
if (!match || !match[1]) {
return { core: value, suffix: null }
}
return { core: match[1], suffix: Number.parseInt(match[2], 10) }
}
const builders = [
(b) => `${b}${random2()}`,
(b) => `${b}_${random3()}`,
(b) => `${b}.${random2()}`,
(b) => `${b}-${random2()}`,
(b) => `${b}${random4()}`,
(b) => `${b}_m${random2()}`,
(b) => `${b}${attempt}`,
]
const trimToMax = (value) =>
value.length > USERNAME_MAX_LENGTH ? value.slice(0, USERNAME_MAX_LENGTH) : value
return builders.map((fn) => {
let candidate = fn(base)
if (candidate.length > USERNAME_MAX_LENGTH) {
candidate = candidate.slice(0, USERNAME_MAX_LENGTH)
}
return candidate
/** پیشنهادهای مشابه: amiri2 → amiri3, amiri5, amiiri3, amiri334 */
const buildCandidates = (base) => {
const { core, suffix } = splitBaseAndSuffix(base)
const candidates = []
if (suffix !== null && !Number.isNaN(suffix)) {
candidates.push(`${core}${suffix + 1}`)
candidates.push(`${core}${suffix + 3}`)
candidates.push(`${core}${suffix}${Math.floor(Math.random() * 9 + 1)}`)
}
const numericSuffixes = [3, 5, 7, 12, 21, 34, 99, 334, 567]
numericSuffixes.forEach((num) => {
candidates.push(`${core}${num}`)
})
for (let i = 1; i < Math.min(core.length, 5); i += 1) {
const ch = core[i]
if (!ch) continue
const doubled = `${core.slice(0, i)}${ch}${core.slice(i)}`
candidates.push(`${doubled}${suffix !== null ? suffix + 1 : 3}`)
candidates.push(`${doubled}${Math.floor(Math.random() * 90 + 10)}`)
}
candidates.push(`${core}_${suffix !== null ? suffix + 1 : 1}`)
candidates.push(`${core}.${Math.floor(Math.random() * 900 + 100)}`)
candidates.push(`${core}-${Math.floor(Math.random() * 90 + 10)}`)
return [...new Set(candidates.map(trimToMax))]
}
const generateUsernameSuggestions = async (
@@ -46,8 +64,8 @@ const generateUsernameSuggestions = async (
const suggestions = []
const tried = new Set([base])
for (let attempt = 1; attempt <= 60 && suggestions.length < count; attempt++) {
const candidates = buildCandidates(base, attempt)
for (let attempt = 0; attempt < 80 && suggestions.length < count; attempt += 1) {
const candidates = buildCandidates(base)
for (const candidate of candidates) {
if (suggestions.length >= count) break
@@ -58,6 +76,15 @@ const generateUsernameSuggestions = async (
const taken = await isUsernameTaken(UserModel, candidate, excludeUserId)
if (!taken) suggestions.push(candidate)
}
if (suggestions.length < count) {
const fallback = trimToMax(`${base}${Math.floor(Math.random() * 9000 + 1000)}`)
if (!tried.has(fallback) && !validateUsername(fallback)) {
tried.add(fallback)
const taken = await isUsernameTaken(UserModel, fallback, excludeUserId)
if (!taken) suggestions.push(fallback)
}
}
}
return suggestions

View File

@@ -1,7 +1,7 @@
/** حروف انگلیسی، اعداد و . _ - — بدون فارسی و کاراکترهای غیرمعمول */
const USERNAME_REGEX = /^[a-zA-Z0-9][a-zA-Z0-9._-]{5,99}$/
const USERNAME_REGEX = /^[a-zA-Z0-9][a-zA-Z0-9._-]{7,99}$/
const USERNAME_MIN_LENGTH = 6
const USERNAME_MIN_LENGTH = 8
const USERNAME_MAX_LENGTH = 100
const validateUsername = (user_name) => {