237 lines
9.1 KiB
JavaScript
237 lines
9.1 KiB
JavaScript
/**
|
||
* @name BetterChatNames
|
||
* @author Break
|
||
* @description Improves chat names by automatically capitalising them and/or removing dashes/underscores
|
||
* @version 1.9.0
|
||
* @authorLink https://github.com/Break-Ben
|
||
* @website https://github.com/Break-Ben/BetterDiscordAddons
|
||
* @source https://github.com/Break-Ben/BetterDiscordAddons/tree/main/BetterChatNames
|
||
* @updateUrl https://raw.githubusercontent.com/Break-Ben/BetterDiscordAddons/main/BetterChatNames/BetterChatNames.plugin.js
|
||
*/
|
||
|
||
const defaultSettings = {
|
||
capitalise: true,
|
||
removeDashes: true,
|
||
removeEmojis: false,
|
||
patchUnrestricted: true,
|
||
capitalOverrides: 'and, or, of, the, for, to, at, a, FAQ, VC, GitHub'
|
||
}
|
||
|
||
const unrestrictedChannels = {
|
||
voice: 2,
|
||
thread: 11,
|
||
stage: 13
|
||
}
|
||
const regex = {
|
||
dash: /-|_/g,
|
||
capital: /(?<=(^|[^\p{L}'’]))\p{L}/gu,
|
||
word: /\b\p{L}+\b/gu,
|
||
emoji: /([-_]?\p{Extended_Pictographic}[-_┃]?)|(\uFE0F\u20E3)/gu
|
||
}
|
||
|
||
let titleObserver
|
||
const { Webpack, Patcher, Utils, Data, UI } = new BdApi('BetterChatNames')
|
||
const { getModule, getByStrings, getByKeys, getByPrototypeKeys } = Webpack
|
||
const { findInTree } = Utils
|
||
const searchOptions = { walkable: ['children', 'props'] } // For Utils.findInTree
|
||
|
||
const currentServer = getByKeys('getLastSelectedGuildId')
|
||
const currentChannel = getByKeys('getLastSelectedChannelId')
|
||
const transitionTo = getByStrings('transitionTo - Transitioning to', { searchExports: true })
|
||
|
||
const sidebar = getModule(m => m?.render?.toString()?.includes('.CHANNEL'))
|
||
const header = getByStrings('.HEADER_BAR)', { defaultExport: false })
|
||
const placeholder = getByPrototypeKeys('getPlaceholder').prototype
|
||
const mention = getByStrings('channelWithIcon', { defaultExport: false })
|
||
|
||
const debounceTimeMs = 1000
|
||
|
||
module.exports = class BetterChatNames {
|
||
start() {
|
||
this.settings = Object.assign({}, defaultSettings, Data.load('settings'))
|
||
this.updateOverrideMap()
|
||
this.observeAppTitle()
|
||
this.patchNames()
|
||
this.refreshChannel()
|
||
}
|
||
|
||
stop() {
|
||
titleObserver.disconnect()
|
||
Patcher.unpatchAll()
|
||
this.refreshChannel()
|
||
}
|
||
|
||
getSettingsPanel = () => UI.buildSettingsPanel({
|
||
settings: [
|
||
{
|
||
id: 'capitalise',
|
||
name: 'Capitalise Words',
|
||
type: 'switch',
|
||
value: this.settings.capitalise
|
||
},
|
||
{
|
||
id: 'removeDashes',
|
||
name: 'Remove Dashes and Underscores',
|
||
type: 'switch',
|
||
value: this.settings.removeDashes
|
||
},
|
||
{
|
||
id: 'removeEmojis',
|
||
name: 'Remove Emojis',
|
||
type: 'switch',
|
||
value: this.settings.removeEmojis
|
||
},
|
||
{
|
||
id: 'patchUnrestricted',
|
||
name: 'Patch Unrestricted Channels',
|
||
note: 'Change the names of channels that can already contain capital letters and spaces, such as voice channels, threads, and stages.',
|
||
type: 'switch',
|
||
value: this.settings.patchUnrestricted
|
||
},
|
||
{
|
||
id: 'capitalOverrides',
|
||
name: 'Capitalisation Overrides',
|
||
note: 'A comma-separated list of words with custom capitalisations. For example: "and, FAQ, GitHub, iPhone". Only enabled if "Capitalise Words" is enabled, and can be cleared to be disabled.',
|
||
type: 'text',
|
||
inline: false,
|
||
value: this.settings.capitalOverrides
|
||
}
|
||
],
|
||
onChange: (_, id, value) => {
|
||
if (id === 'capitalOverrides') {
|
||
clearTimeout(this.debounceTimer)
|
||
this.debounceTimer = setTimeout(() => {
|
||
this.settings[id] = value
|
||
Data.save('settings', this.settings)
|
||
this.updateOverrideMap()
|
||
if (this.settings.capitalise)
|
||
this.refreshChannel()
|
||
}, debounceTimeMs)
|
||
return
|
||
}
|
||
|
||
this.settings[id] = value
|
||
Data.save('settings', this.settings)
|
||
this.refreshChannel()
|
||
}
|
||
})
|
||
|
||
observeAppTitle() {
|
||
let lastUnpatchedAppTitle
|
||
titleObserver = new MutationObserver(() => {
|
||
if (document.title !== lastUnpatchedAppTitle) { // Resolves conflicts with EditChannels' MutationObserver
|
||
lastUnpatchedAppTitle = document.title
|
||
this.patchAppTitle()
|
||
}
|
||
})
|
||
titleObserver.observe(document.querySelector('title'), { childList: true })
|
||
}
|
||
|
||
patchAppTitle() {
|
||
const patchedTitle = this.patchText(document.title)
|
||
|
||
if (currentServer?.getGuildId() && document.title !== patchedTitle) // If in server and title not already patched
|
||
document.title = patchedTitle
|
||
}
|
||
|
||
patchNames() {
|
||
this.patchSidebar()
|
||
this.patchHeader()
|
||
this.patchChatPlaceholder()
|
||
this.patchMention()
|
||
}
|
||
|
||
patchSidebar() {
|
||
Patcher.after(sidebar, 'render', (_, __, data) => {
|
||
const channelName = findInTree(data, prop => prop?.name, searchOptions)
|
||
const channelType = findInTree(data, prop => prop?.channel, searchOptions).channel.type
|
||
|
||
if (!Object.values(unrestrictedChannels).includes(channelType) || this.settings.patchUnrestricted) // If not a voice/stage channel or patchUnrestricted is enabled
|
||
channelName.name = this.patchText(channelName.name)
|
||
})
|
||
}
|
||
|
||
patchHeader() {
|
||
Patcher.after(header, 'A', (_, __, data) => {
|
||
const rootChannel = findInTree(data, prop => prop?.level, searchOptions)
|
||
if (!rootChannel)
|
||
return
|
||
|
||
if (rootChannel.level === 1) // If not in thread
|
||
rootChannel.children.props.children[2] = this.patchText(rootChannel.children.props.children[2])
|
||
else if (rootChannel.level === 2) { // If in thread
|
||
rootChannel.children = this.patchText(rootChannel.children)
|
||
if (this.settings.patchUnrestricted) {
|
||
const threadName = findInTree(data, prop => Array.isArray(prop) && prop[1] === ' ', searchOptions)
|
||
threadName[2] = this.patchText(threadName[2])
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
patchChatPlaceholder() {
|
||
Patcher.after(placeholder, 'render', (_, __, data) => {
|
||
const textArea = findInTree(data, prop => prop.channel, searchOptions)
|
||
const isThread = textArea.channel.type === unrestrictedChannels.thread
|
||
|
||
if (textArea.channel.guild_id && (!isThread || this.settings.patchUnrestricted) && !textArea.disabled && textArea.type.analyticsName === 'normal') { // If in a server, not in a thread (or patchUnrestricted is enabled), can message and not editing a message
|
||
const splitIndex = (isThread ? textArea.placeholder.indexOf('"') : textArea.placeholder.indexOf('#')) + 1 // Indicates where the channel name starts
|
||
textArea.placeholder = textArea.placeholder.substring(0, splitIndex) + this.patchText(textArea.placeholder.substr(splitIndex))
|
||
}
|
||
})
|
||
}
|
||
|
||
patchMention() {
|
||
Patcher.after(mention, 'A', (_, __, data) => {
|
||
const channelName = findInTree(data, prop => typeof prop.children === 'string', searchOptions)
|
||
|
||
if (data.props.className.includes('iconMentionText') || this.settings.patchUnrestricted) // If is a normal chat mention (not a thread) or patchUnrestricted is enabled
|
||
channelName.children = this.patchText(channelName.children)
|
||
})
|
||
}
|
||
|
||
patchText(text) {
|
||
if (this.settings.removeEmojis)
|
||
text = text.replace(regex.emoji, '')
|
||
|
||
if (this.settings.removeDashes)
|
||
text = text.replace(regex.dash, ' ')
|
||
|
||
if (this.settings.capitalise) {
|
||
text = text.replace(regex.capital, letter => letter.toUpperCase())
|
||
|
||
if (this.settings.capitalOverrides) {
|
||
let wordCount = 0
|
||
text = text.replace(regex.word, word => {
|
||
wordCount++
|
||
const wordLower = word.toLowerCase()
|
||
if (!this.overrideMap.has(wordLower))
|
||
return word
|
||
|
||
const wordOverride = this.overrideMap.get(wordLower)
|
||
if (wordCount == 1 && wordOverride === wordLower)
|
||
return word
|
||
|
||
return wordOverride
|
||
})
|
||
}
|
||
}
|
||
|
||
return text
|
||
}
|
||
|
||
refreshChannel() {
|
||
const currentServerId = currentServer?.getGuildId()
|
||
const currentChannelId = currentChannel?.getChannelId()
|
||
|
||
if (currentServerId) { // If not in a DM
|
||
transitionTo('/channels/@me')
|
||
setImmediate(() => transitionTo(`/channels/${currentServerId}/${currentChannelId}`))
|
||
}
|
||
}
|
||
|
||
updateOverrideMap() {
|
||
// Maps lowercase words to their custom capitalised versions
|
||
this.overrideMap = new Map((this.settings.capitalOverrides).split(',').map(w => w.trim()).filter(w => w).map(w => [w.toLowerCase(), w]))
|
||
}
|
||
} |