Files
arindOS/dotfiles/.config/BetterDiscord/plugins/BetterChatNames.plugin.js
T

237 lines
9.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @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]))
}
}