/**
 * @name VoiceActivity
 * @author Neodymium
 * @version 1.12.1
 * @description Shows icons and info in popouts, the member list, and more when someone is in a voice channel.
 * @source https://github.com/Neodymium7/BetterDiscordStuff/blob/main/VoiceActivity/VoiceActivity.plugin.js
 * @invite fRbsqH87Av
 */

/*@cc_on
@if (@_jscript)

	// Offer to self-install for clueless users that try to run this directly.
	var shell = WScript.CreateObject("WScript.Shell");
	var fs = new ActiveXObject("Scripting.FileSystemObject");
	var pathPlugins = shell.ExpandEnvironmentStrings("%APPDATA%\BetterDiscord\plugins");
	var pathSelf = WScript.ScriptFullName;
	// Put the user at ease by addressing them in the first person
	shell.Popup("It looks like you've mistakenly tried to run me directly. \n(Don't do that!)", 0, "I'm a plugin for BetterDiscord", 0x30);
	if (fs.GetParentFolderName(pathSelf) === fs.GetAbsolutePathName(pathPlugins)) {
		shell.Popup("I'm in the correct folder already.", 0, "I'm already installed", 0x40);
	} else if (!fs.FolderExists(pathPlugins)) {
		shell.Popup("I can't find the BetterDiscord plugins folder.\nAre you sure it's even installed?", 0, "Can't install myself", 0x10);
	} else if (shell.Popup("Should I copy myself to BetterDiscord's plugins folder for you?", 0, "Do you need some help?", 0x34) === 6) {
		fs.CopyFile(pathSelf, fs.BuildPath(pathPlugins, fs.GetFileName(pathSelf)), true);
		// Show the user where to put plugins in the future
		shell.Exec("explorer " + pathPlugins);
		shell.Popup("I'm installed!", 0, "Successfully installed", 0x40);
	}
	WScript.Quit();

@else@*/

'use strict';

const betterdiscord = new BdApi("VoiceActivity");
const react = BdApi.React;

// styles
let _styles = "";
function _loadStyle(path, css) {
	_styles += "/*" + path + "*/\n" + css + "\n";
}
function styles() {
	return _styles;
}

// @lib/settings.ts
class SettingsManager {
	settings = betterdiscord.Data.load("settings");
	listeners = new Set();
	constructor(defaultSettings) {
		if (!this.settings) {
			betterdiscord.Data.save("settings", defaultSettings);
			this.settings = defaultSettings;
			return;
		}
		if (Object.keys(this.settings) !== Object.keys(defaultSettings)) {
			this.settings = { ...defaultSettings, ...this.settings };
			let changed = false;
			for (const key in this.settings) {
				if (!(key in defaultSettings)) {
					delete this.settings[key];
					changed = true;
				}
			}
			if (changed) betterdiscord.Data.save("settings", this.settings);
		}
	}
	addListener(listener) {
		this.listeners.add(listener);
		return () => {
			this.listeners.delete(listener);
		};
	}
	clearListeners() {
		this.listeners.clear();
	}
	useSettingsState(...keys) {
		let initialState = this.settings;
		if (keys.length) initialState = Object.fromEntries(keys.map((key) => [key, initialState[key]]));
		const [state, setState] = react.useState(initialState);
		react.useEffect(() => {
			return this.addListener((key, value) => {
				if (!keys.length || keys.includes(key)) setState((state2) => ({ ...state2, [key]: value }));
			});
		}, []);
		return state;
	}
	get(key) {
		return this.settings[key];
	}
	set(key, value) {
		this.settings[key] = value;
		betterdiscord.Data.save("settings", this.settings);
		for (const listener of this.listeners) listener(key, value);
	}
}
function buildSettingsPanel(settingsManager, settings) {
	for (const setting of settings) {
		setting.value = settingsManager.get(setting.id);
	}
	return betterdiscord.UI.buildSettingsPanel({
		settings,
		onChange: (_, id, value) => settingsManager.set(id, value)
	});
}

// @lib/strings.ts
const LocaleStore = betterdiscord.Webpack.getStore("LocaleStore");
class StringsManager {
	locales;
	defaultLocale;
	strings;
	constructor(locales, defaultLocale) {
		this.locales = locales;
		this.defaultLocale = defaultLocale;
		this.strings = locales[defaultLocale];
	}
	setLocale = () => {
		this.strings = this.locales[LocaleStore.locale] || this.locales[this.defaultLocale];
	};
	subscribe() {
		this.setLocale();
		LocaleStore.addReactChangeListener(this.setLocale);
	}
	unsubscribe() {
		LocaleStore.removeReactChangeListener(this.setLocale);
	}
	get(key) {
		return this.strings[key] || this.locales[this.defaultLocale][key];
	}
}

// @lib/changelog.ts
function showChangelog(changes, meta) {
	if (!changes || changes.length == 0) return;
	const changelogVersion = betterdiscord.Data.load("changelogVersion");
	if (meta.version === changelogVersion) return;
	betterdiscord.UI.showChangelogModal({
		title: meta.name,
		subtitle: meta.version,
		changes
	});
	betterdiscord.Data.save("changelogVersion", meta.version);
}

// @lib/utils/webpack.ts
function getClasses(...classes) {
	return betterdiscord.Webpack.getModule((m) => betterdiscord.Webpack.Filters.byKeys(...classes)(m) && typeof m[classes[0]] == "string");
}
function getSelectors(...classes) {
	const module = getClasses(...classes);
	if (!module) return void 0;
	return classes.reduce((obj, className) => {
		obj[className] = "." + module[className].replaceAll(" ", ".");
		return obj;
	}, {});
}
function getIcon(searchString) {
	const filter = (m) => betterdiscord.Webpack.Filters.byStrings(searchString, '"svg"')(m) && typeof m === "function";
	return betterdiscord.Webpack.getModule(filter, {
		searchExports: true
	});
}
function expect(object, options) {
	if (object) return object;
	const fallbackMessage = !options.fatal && options.fallback ? " Using fallback value instead." : "";
	const errorMessage = `Module ${options.name} not found.${fallbackMessage}\n\nContact the plugin developer to inform them of this error.`;
	betterdiscord.Logger.error(errorMessage);
	options.onError?.();
	if (options.fatal) throw new Error(errorMessage);
	return options.fallback;
}
function expectModule(options) {
	return expect(betterdiscord.Webpack.getModule(options.filter, options), options);
}
function expectWithKey(options) {
	const [module, key] = betterdiscord.Webpack.getWithKey(options.filter, options);
	if (module) return [module, key];
	const fallback = expect(module, options);
	if (fallback) {
		const key2 = "__key";
		return [{ [key2]: fallback }, key2];
	}
	return void 0;
}
function expectSelectors(name, classes) {
	return expect(getSelectors(...classes), {
		name
	});
}
function expectIcon(name, searchString) {
	return expect(getIcon(searchString), {
		name,
		fallback: (_props) => null
	});
}
function byType(type) {
	return (e) => typeof e === type;
}

// manifest.json
const changelog = [
	{
		title: "Fixed",
		type: "fixed",
		items: [
			"Fixed member list icons."
		]
	}
];

// @discord/stores.ts
const UserStore = betterdiscord.Webpack.getStore("UserStore");
const VoiceStateStore = betterdiscord.Webpack.getStore("VoiceStateStore");
const GuildStore = betterdiscord.Webpack.getStore("GuildStore");
const ChannelStore = betterdiscord.Webpack.getStore("ChannelStore");
const PermissionStore = betterdiscord.Webpack.getStore("PermissionStore");
const useStateFromStores = expectModule({
	filter: betterdiscord.Webpack.Filters.byStrings("useStateFromStores"),
	name: "Flux",
	fallback(stores, callback) {
		return callback();
	},
	searchExports: true
});

// modules/discordmodules.tsx
function useUserVoiceStateFallback({ userId }) {
	const voiceState = useStateFromStores(
		[VoiceStateStore],
		() => userId && VoiceStateStore.getDiscoverableVoiceStateForUser(userId)
	);
	const channel = useStateFromStores([ChannelStore], () => {
		if (voiceState?.channelId) return ChannelStore.getChannel(voiceState?.channelId);
	});
	const visible = useStateFromStores(
		[PermissionStore],
		() => channel?.isPrivate() || PermissionStore.can(Permissions.VIEW_CHANNEL, channel)
	);
	if (visible) {
		return {
			voiceState,
			voiceChannel: channel
		};
	} else return {};
}
const MemberListItem = expectModule({
	filter: (m) => m.type?.toString?.().includes("MemberListItem"),
	name: "MemberListItem",
	searchExports: true
});
const PrivateChannel = expectWithKey({
	filter: betterdiscord.Webpack.Filters.byStrings("PrivateChannel", "getTypingUsers"),
	name: "PrivateChannel",
	defaultExport: false
});
const PeopleListItem = expectModule({
	filter: (m) => m?.prototype?.render && betterdiscord.Webpack.Filters.byStrings("this.peopleListItemRef")(m),
	name: "PeopleListItem"
});
const Permissions = expectModule({
	filter: betterdiscord.Webpack.Filters.byKeys("VIEW_CREATOR_MONETIZATION_ANALYTICS"),
	searchExports: true,
	name: "Permissions",
	fallback: {
		VIEW_CHANNEL: 1024n
	}
});
const memberSelectors = expectSelectors("Children Class", ["avatar", "children", "layout"]);
const useUserVoiceState = expectModule({
	filter: betterdiscord.Webpack.Filters.byStrings("getDiscoverableVoiceState", "getDiscoverableVoiceStateForUser"),
	name: "useUserVoiceState",
	fallback: useUserVoiceStateFallback
});

// locales.json
const el = {
	SETTINGS_PROFILE: "Τομέας Προφίλ",
	SETTINGS_PROFILE_NOTE: "Εμφανίζει τον τομέα προφίλ για την τρέχουσα δραστηριότητα φωνής στα αναδυόμενα χρήστη και στην πλευρικές μπάρες προφίλ των Άμεσων Μηνυμάτων.",
	SETTINGS_ICONS: "Εικονίδια Λίστας Μελών",
	SETTINGS_ICONS_NOTE: "Εμφανίζει εικονίδια στη λίστα μελών όταν κάποιος είναι σε κανάλι φωνής.",
	SETTINGS_DM_ICONS: "Εικονίδια Άμεσων Μηνυμάτων",
	SETTINGS_DM_ICONS_NOTE: "Εμφανίζει εικονίδια στη λίστα Άμεσων Μηνυμάτων όταν κάποιος είναι σε κανάλι φωνής.",
	SETTINGS_PEOPLE_ICONS: "Εικονίδια Λίστας Φίλων",
	SETTINGS_PEOPLE_ICONS_NOTE: "Εμφανίζει εικονίδια στη λίστα φίλων όταν κάποιος είναι σε κανάλι φωνής.",
	SETTINGS_GUILD_ICONS: "Εικονίδια Συντεχνίας",
	SETTINGS_GUILD_ICONS_NOTE: "Εμφανίζει εικονίδια στις συντεχνίες ακόμα και αν δεν συμμετέχετε.",
	SETTINGS_COLOR: "Λίστα Μελών - Χρώμα Εικονιδίου Τρέχοντος Καναλιού",
	SETTINGS_COLOR_NOTE: "Αλλάζει τα εικονίδια της Λίστας Μελών σε πράσινα όταν ο χρήστης είναι στο δικό σας τρέχον κανάλι φωνής.",
	SETTINGS_STATUS: "Λίστα Μελών - Εμφάνιση Εικονιδίων Κατάστασης",
	SETTINGS_STATUS_NOTE: "Αλλάζει τα εικονίδια της Λίστας Μελών όταν ο χρήστης είναι σε Σίγαση, Κώφωση ή έχε ενεργοποιημένο Βίντεο.",
	SETTINGS_IGNORE: "Αγνόηση",
	SETTINGS_IGNORE_NOTE: "Προσθέτει μια επιλογή στο Κανάλι Φωνής και στα μενού περιεχομένου Συντεχνίας για αγνόηση αυτού του καναλιού/συντεχνίας στα Εικονίδια Λϊστας Μελών και στα Αναδυόμενα Χρήστη.",
	CONTEXT_IGNORE: "Αγνόηση στη Δραστηριότητα Φωνής",
	VOICE_CALL: "Φωνητική Κλήση",
	PRIVATE_CALL: "Ιδιωτική Κλήση",
	GROUP_CALL: "Ομαδική Κλήση",
	LIVE: "Ζωντανά",
	HEADER: "Σε ένα Κανάλι Φωνής",
	HEADER_VOICE: "Σε μια Κλήση Φωνής",
	HEADER_PRIVATE: "Σε μια Ιδιωτική Κλήση",
	HEADER_GROUP: "Σε μια Ομαδική Κλήση",
	HEADER_STAGE: "Σε ένα Κανάλι Σταδίου",
	VIEW: "Προβολή Καναλιού",
	VIEW_CALL: "Προβολή Κλήσης",
	JOIN: "Συμμετοχή σε Κανάλι",
	JOIN_CALL: "Συμμετοχή σε Κλήση",
	JOIN_DISABLED: "Ήδη σε Κανάλι",
	JOIN_DISABLED_CALL: "Ήδη σε Κλήση",
	JOIN_VIDEO: "Συμμετοχή με Βίντεο"
};
const ru = {
	SETTINGS_PROFILE: "Раздел в Профиле",
	SETTINGS_PROFILE_NOTE: "Показывает раздел для текущей голосовой активности в профиле, всплывающих окнах и боковых панелях в ЛС.",
	SETTINGS_ICONS: "Иконки в Списке Участников",
	SETTINGS_ICONS_NOTE: "Показывает иконки в списке участников когда пользователь в голосовом канале.",
	SETTINGS_DM_ICONS: "Иконки в ЛС",
	SETTINGS_DM_ICONS_NOTE: "Показывает иконки в списке участников ЛС когда пользователь в голосовом канале.",
	SETTINGS_PEOPLE_ICONS: "Иконки в Списке Друзей",
	SETTINGS_PEOPLE_ICONS_NOTE: "Показывает иконки в списке друзей когда пользователь в голосовом канале.",
	SETTINGS_GUILD_ICONS: "Иконки Сервера",
	SETTINGS_GUILD_ICONS_NOTE: "Показывает голосовые иконки на серверах даже когда вы не участвуете.",
	SETTINGS_COLOR: "Цвет Иконки Голосовой Активности",
	SETTINGS_COLOR_NOTE: "Делает иконки в Списке Участников зелёными когда пользователь в голосовом канале вместе с вами.",
	SETTINGS_STATUS: "Показывать Иконки Статуса",
	SETTINGS_STATUS_NOTE: "Меняет иконки в Списке Участников когда пользователь Выключил Микрофон/Звук или Начал трансляцию.",
	SETTINGS_IGNORE: "Ингор",
	SETTINGS_IGNORE_NOTE: "Добавляет возможность в контекстных меню Голосовых Каналов и Серверов игнорировать этот канал/сервер в Списке Участников и Профилях Пользователей.",
	CONTEXT_IGNORE: "Игнор (Голосовая Активность)",
	VOICE_CALL: "Голосовой звонок",
	PRIVATE_CALL: "Приватный звонок",
	GROUP_CALL: "Групповой звонок",
	LIVE: "В ЭФИРЕ",
	HEADER: "В Голосовом Канале",
	HEADER_VOICE: "В Голосовом Звонке",
	HEADER_PRIVATE: "В Приватном Звонке",
	HEADER_GROUP: "В Групповом Звонке",
	HEADER_STAGE: "В Канале Трибуны",
	VIEW: "Просмотреть Канал",
	VIEW_CALL: "Просмотреть Звонок",
	JOIN: "Присоединиться к Каналу",
	JOIN_CALL: "Присоединиться к Звонку",
	JOIN_DISABLED: "Уже в Канале",
	JOIN_DISABLED_CALL: "Уже в Звонке",
	JOIN_VIDEO: "Присоединиться с Видео"
};
const de = {
	SETTINGS_PROFILE: "Profilbereich",
	SETTINGS_PROFILE_NOTE: "Zeigt den Profilbereich für die aktuelle Sprachaktivität in Benutzer-Popouts und DM-Profilseitenleisten an.",
	SETTINGS_ICONS: "Symbole in der Mitgliederliste",
	SETTINGS_ICONS_NOTE: "Zeigt Symbole in der Mitgliederliste an, wenn sich jemand in einem Sprachkanal befindet.",
	SETTINGS_DM_ICONS: "DM-Symbole",
	SETTINGS_DM_ICONS_NOTE: "Zeigt Symbole in der DM-Liste an, wenn sich jemand in einem Sprachkanal befindet.",
	SETTINGS_PEOPLE_ICONS: "Symbole in der Freundesliste",
	SETTINGS_PEOPLE_ICONS_NOTE: "Zeigt Symbole in der Freundesliste an, wenn sich jemand in einem Sprachkanal befindet.",
	SETTINGS_GUILD_ICONS: "Gildensymbole",
	SETTINGS_GUILD_ICONS_NOTE: "Zeigt Symbole für Gilden an, auch wenn du nicht teilnimmst.",
	SETTINGS_COLOR: "Mitgliederliste - Farbe des aktuellen Kanalsymbols",
	SETTINGS_COLOR_NOTE: "Die Symbole in der Mitgliederliste werden grün, wenn sich der Benutzer in Ihrem aktuellen Sprachkanal befindet.",
	SETTINGS_STATUS: "Mitgliederliste - Statussymbole anzeigen",
	SETTINGS_STATUS_NOTE: "Ändert die Symbole in der Mitgliederliste, wenn ein Benutzer stummgeschaltet oder mit aktiviertem Video ist.",
	SETTINGS_IGNORE: "Ignorieren",
	SETTINGS_IGNORE_NOTE: "Fügt eine Option in die Kontextmenüs von Sprachkanälen und Gilden hinzu, um diesen Kanal/diese Gilde in der Mitgliederliste und Benutzer-Popouts zu ignorieren.",
	CONTEXT_IGNORE: "Bei Sprachaktivität ignorieren",
	VOICE_CALL: "Sprachanruf",
	PRIVATE_CALL: "Privatanruf",
	GROUP_CALL: "Gruppenanruf",
	LIVE: "Live",
	HEADER: "In einem Sprachkanal",
	HEADER_VOICE: "In einem Sprachanruf",
	HEADER_PRIVATE: "In einem privaten Anruf",
	HEADER_GROUP: "In einem Gruppenruf",
	HEADER_STAGE: "In einem Stage Kanal",
	VIEW: "Kanal anzeigen",
	VIEW_CALL: "Anruf anzeigen",
	JOIN: "Kanal beitreten",
	JOIN_CALL: "Anruf beitreten",
	JOIN_DISABLED: "Bereits im Kanal",
	JOIN_DISABLED_CALL: "Bereits im Aufruf",
	JOIN_VIDEO: "Beitreten mit Video"
};
const fr = {
	SETTINGS_PROFILE: "Section de profil",
	SETTINGS_PROFILE_NOTE: "Affiche une section de profile pour l'activité vocale actuelle dans les popouts utilisateur et les profiles latéraux de MP.",
	SETTINGS_ICONS: "Icônes de la liste de membre",
	SETTINGS_ICONS_NOTE: "Affiche des icônes dans la liste de membre quand quelqu'un est dans un salon vocal.",
	SETTINGS_DM_ICONS: "Icônes de MP",
	SETTINGS_DM_ICONS_NOTE: "Affiche des icônes dans la liste de MP quand quelqu'un est dans un salon vocal.",
	SETTINGS_PEOPLE_ICONS: "Icônes de la liste d'amis",
	SETTINGS_PEOPLE_ICONS_NOTE: "Affiche des icônes dans la liste d'amis quand quelqu'un est dans un salon vocal.",
	SETTINGS_GUILD_ICONS: "Icônes de serveur",
	SETTINGS_GUILD_ICONS_NOTE: "Affiche des icônes de vocal sur les serveurs même quand vous ne participez pas.",
	SETTINGS_COLOR: "Liste de membre - Couleur d'icône du salon actuel",
	SETTINGS_COLOR_NOTE: "Rends les icônes de la liste de membre vertes quand un utilisateur est dans le salon vocal actuel.",
	SETTINGS_STATUS: "Liste de membre - Afficher les icônes de status",
	SETTINGS_STATUS_NOTE: "Change les icônes de la liste de membre quand un utilisateur est muet, mis en sourdine, ou a la vidéo activée.",
	SETTINGS_IGNORE: "Ignorer",
	SETTINGS_IGNORE_NOTE: "Ajoute une option dans les menus contextuels des salons vocaux et serveurs pour ignorer le dit salon/serveur dans les icônes de liste de membre et popouts utilisateur.",
	CONTEXT_IGNORE: "Ignorer dans Activité Vocale",
	VOICE_CALL: "Appel vocal",
	PRIVATE_CALL: "Appel privé",
	GROUP_CALL: "Appel de groupe",
	LIVE: "Live",
	HEADER: "Dans un salon vocal",
	HEADER_VOICE: "Dans un appel vocal",
	HEADER_PRIVATE: "Dans un appel privé",
	HEADER_GROUP: "Dans un appel de groupe",
	HEADER_STAGE: "Dans un salon de conférence",
	VIEW: "Voir le salon",
	VIEW_CALL: "Voir l'appel",
	JOIN: "Rejoindre le salon",
	JOIN_CALL: "Rejoindre l'appel",
	JOIN_DISABLED: "Déjà dans le salon",
	JOIN_DISABLED_CALL: "Déjà en appel",
	JOIN_VIDEO: "Rejoidre avec vidéo",
	MEMBER: "Membre",
	MEMBERS: "Membres"
};
const locales = {
	"en-US": {
	SETTINGS_PROFILE: "Profile Section",
	SETTINGS_PROFILE_NOTE: "Shows profile section for current voice activity in user popouts and DM profile sidebars.",
	SETTINGS_ICONS: "Member List Icons",
	SETTINGS_ICONS_NOTE: "Shows icons on the member list when someone is in a voice channel.",
	SETTINGS_DM_ICONS: "DM Icons",
	SETTINGS_DM_ICONS_NOTE: "Shows icons on the DM list when someone is in a voice channel.",
	SETTINGS_PEOPLE_ICONS: "Friends List Icons",
	SETTINGS_PEOPLE_ICONS_NOTE: "Shows icons on the friends list when someone is in a voice channel.",
	SETTINGS_GUILD_ICONS: "Server Icons",
	SETTINGS_GUILD_ICONS_NOTE: "Shows voice icons on servers even when you're not participating.",
	SETTINGS_COLOR: "Member List - Current Channel Icon Color",
	SETTINGS_COLOR_NOTE: "Makes the Member List icons green when the user is in your current voice channel.",
	SETTINGS_STATUS: "Member List - Show Status Icons",
	SETTINGS_STATUS_NOTE: "Changes the Member List icons when a user is Muted, Deafened, or has Video enabled.",
	SETTINGS_CURRENT_USER: "Current User Icon",
	SETTINGS_CURRENT_USER_NOTE: "Toggles displaying a voice channel icon for the current user.",
	SETTINGS_IGNORE: "Ignore",
	SETTINGS_IGNORE_NOTE: "Adds an option on Voice Channel and Server context menus to ignore that channel/server in Member List Icons and User Popouts.",
	CONTEXT_IGNORE: "Ignore in Voice Activity",
	VOICE_CALL: "Voice Call",
	PRIVATE_CALL: "Private Call",
	GROUP_CALL: "Group Call",
	LIVE: "Live",
	HEADER: "In a Voice Channel",
	HEADER_VOICE: "In a Voice Call",
	HEADER_PRIVATE: "In a Private Call",
	HEADER_GROUP: "In a Group Call",
	HEADER_STAGE: "In a Stage Channel",
	VIEW: "View Channel",
	VIEW_CALL: "View Call",
	JOIN: "Join Channel",
	JOIN_CALL: "Join Call",
	JOIN_DISABLED: "Already in Channel",
	JOIN_DISABLED_CALL: "Already in Call",
	JOIN_VIDEO: "Join With Video",
	MEMBER: "Member",
	MEMBERS: "Members"
},
	el: el,
	ru: ru,
	de: de,
	fr: fr
};

// modules/utils.ts
const Settings = new SettingsManager({
	showMemberListIcons: true,
	showDMListIcons: true,
	showPeopleListIcons: true,
	currentChannelColor: true,
	showStatusIcons: true,
	currentUserIcon: true,
	ignoreEnabled: false,
	ignoredChannels: [],
	ignoredGuilds: []
});
const Strings = new StringsManager(locales, "en-US");
function groupDMName(members) {
	if (members.length === 1) {
		return UserStore.getUser(members[0]).globalName;
	} else if (members.length > 1) {
		let name = "";
		for (let i = 0; i < members.length; i++) {
			if (i === members.length - 1) name += UserStore.getUser(members[i]).globalName;
			else name += UserStore.getUser(members[i]).globalName + ", ";
		}
		return name;
	}
	return "Unnamed";
}

// styles/voiceicon.module.css
const css = `
.VoiceActivity-voiceicon-icon {
	height: 20px;
	width: 20px;
	min-width: 20px;
	border-radius: 50%;
	background-color: var(--background-accent);
	cursor: pointer;
	svg {
		padding: 3px;
		color: #fff;
	}
}
.VoiceActivity-voiceicon-iconCurrentCall {
	background-color: var(--status-positive);
}
.VoiceActivity-voiceicon-iconLive {
	height: 16px;
	border-radius: 16px;
	background-color: var(--red-400);
	color: #fff;
	font-size: 12px;
	line-height: 16px;
	font-weight: 600;
	font-family: var(--font-display);
	text-transform: uppercase;
	& > div {
		padding: 0 6px;
	}
}
.VoiceActivity-voiceicon-tooltip {
	.VoiceActivity-voiceicon-header {
		display: block;
		overflow: hidden;
		white-space: nowrap;
		text-overflow: ellipsis;
	}
	.VoiceActivity-voiceicon-subtext {
		display: flex;
		flex-direction: row;
		margin-top: 3px;
		& > div {
			overflow: hidden;
			white-space: nowrap;
			text-overflow: ellipsis;
		}
	}
	.VoiceActivity-voiceicon-tooltipIcon {
		min-width: 16px;
		margin-right: 3px;
		color: var(--interactive-normal);
	}
}
.VoiceActivity-voiceicon-iconContainer {
	margin-left: auto;
}
.VoiceActivity-voiceicon-peopleListIcon {
	margin-right: 16px;
}`;
_loadStyle("voiceicon.module.css", css);
const modules_1af761ba = {
	"icon": "VoiceActivity-voiceicon-icon",
	"iconCurrentCall": "VoiceActivity-voiceicon-iconCurrentCall",
	"iconLive": "VoiceActivity-voiceicon-iconLive",
	"tooltip": "VoiceActivity-voiceicon-tooltip",
	"header": "VoiceActivity-voiceicon-header",
	"subtext": "VoiceActivity-voiceicon-subtext",
	"tooltipIcon": "VoiceActivity-voiceicon-tooltipIcon",
	"iconContainer": "VoiceActivity-voiceicon-iconContainer",
	"peopleListIcon": "VoiceActivity-voiceicon-peopleListIcon"
};
const iconStyles = modules_1af761ba;

// @discord/icons.tsx
const Speaker = expectIcon("Speaker", "M12 3a1 1 0 0 0-1-1h-.06a1 1 0 0 0-.74.32L5.92 7H3a1 1");
const Muted = expectIcon(
	"Muted",
	"m2.7 22.7 20-20a1 1 0 0 0-1.4-1.4l-20 20a1 1 0 1 0 1.4 1.4ZM10.8 17.32c-.21.21-.1.58.2.62V20H9a1"
);
const ServerMuted = expectIcon(
	"ServerMuted",
	"M21.76.83a5.02 5.02 0 0 1 .78 7.7 5 5 0 0 1-7.07 0 5.02 5.02 0 0 1 0-7.07"
);
const Deafened = expectIcon(
	"Deafened",
	"M22.7 2.7a1 1 0 0 0-1.4-1.4l-20 20a1 1 0 1 0 1.4 1.4l20-20ZM17.06"
);
const ServerDeafened = expectIcon(
	"ServerDeafened",
	"M12.38 1c.38.02.58.45.4.78-.15.3-.3.62-.4.95A.4.4 0 0 1 12 3a9 9 0 0 0-8.95 10h1.87a5"
);
const Video = expectIcon("Video", "M4 4a3 3 0 0 0-3 3v10a3 3 0 0 0 3 3h11a3 3");
const ChannelIcon = expectModule({
	filter: betterdiscord.Webpack.Filters.combine(
		betterdiscord.Webpack.Filters.byStrings("isGuildStageVoice", "isGroupDM", "isPrivate"),
		(m) => !m.toString?.().includes("intl")
	),
	name: "ChannelIcon",
	fallback: (_props) => null
});

// @discord/modules.ts
const transitionTo = expectModule({
	filter: betterdiscord.Webpack.Filters.combine(
		betterdiscord.Webpack.Filters.byStrings("transitionTo -"),
		byType("function")
	),
	searchExports: true,
	name: "transitionTo"
});

// components/VoiceIcon.tsx
function VoiceIcon(props) {
	const settingsState = Settings.useSettingsState(
		"showMemberListIcons",
		"showDMListIcons",
		"showPeopleListIcons",
		"ignoreEnabled",
		"ignoredChannels",
		"ignoredGuilds",
		"currentChannelColor",
		"showStatusIcons",
		"currentUserIcon"
	);
	const currentUser = UserStore.getCurrentUser();
	const { voiceState, voiceChannel: channel } = useUserVoiceState({ userId: props.userId });
	const { voiceState: currentUserVoiceState } = useUserVoiceState({ userId: currentUser?.id });
	if (props.context === "memberlist" && !settingsState.showMemberListIcons) return null;
	if (props.context === "dmlist" && !settingsState.showDMListIcons) return null;
	if (props.context === "peoplelist" && !settingsState.showPeopleListIcons) return null;
	if (props.userId === currentUser?.id && !settingsState.currentUserIcon) return null;
	if (!voiceState) return null;
	const guild = GuildStore.getGuild(channel.guild_id);
	const ignored = settingsState.ignoredChannels.includes(channel.id) || settingsState.ignoredGuilds.includes(guild?.id);
	if (settingsState.ignoreEnabled && ignored) return null;
	let text;
	let subtext;
	let channelPath;
	let className = iconStyles.icon;
	if (settingsState.currentChannelColor && channel.id === currentUserVoiceState?.channelId)
		className = `${iconStyles.icon} ${iconStyles.iconCurrentCall}`;
	if (voiceState.selfStream) className = iconStyles.iconLive;
	if (guild) {
		text = guild.name;
		subtext = channel.name;
		channelPath = `/channels/${guild.id}/${channel.id}`;
	} else {
		text = channel.name;
		subtext = Strings.get("VOICE_CALL");
		channelPath = `/channels/@me/${channel.id}`;
	}
	switch (channel.type) {
		case 1:
			text = UserStore.getUser(channel.recipients[0]).globalName;
			subtext = Strings.get("PRIVATE_CALL");
			break;
		case 3:
			text = channel.name || groupDMName(channel.recipients);
			subtext = Strings.get("GROUP_CALL");
			break;
	}
	let Icon = Speaker;
	if (settingsState.showStatusIcons) {
		if (voiceState.selfVideo) Icon = Video;
		else if (voiceState.deaf) Icon = ServerDeafened;
		else if (voiceState.selfDeaf) Icon = Deafened;
		else if (voiceState.mute) Icon = ServerMuted;
		else if (voiceState.selfMute) Icon = Muted;
	}
	return BdApi.React.createElement(
		"div",
		{
			className,
			onClick: (e) => {
				e.stopPropagation();
				e.preventDefault();
				if (channelPath) transitionTo?.(channelPath);
			}
		},
		BdApi.React.createElement(
			betterdiscord.Components.Tooltip,
			{
				text: BdApi.React.createElement("div", { className: iconStyles.tooltip }, BdApi.React.createElement("div", { className: iconStyles.header, style: { fontWeight: "600" } }, text), BdApi.React.createElement("div", { className: iconStyles.subtext }, BdApi.React.createElement(
					ChannelIcon,
					{
						className: iconStyles.tooltipIcon,
						size: "16",
						width: "16",
						height: "16",
						color: "currentColor",
						channel
					}
				), BdApi.React.createElement("div", { style: { fontWeight: "400" } }, subtext)))
			},
			(props2) => BdApi.React.createElement("div", { ...props2 }, !voiceState.selfStream ? BdApi.React.createElement(Icon, { size: "14", width: "14", height: "14", color: "currentColor" }) : Strings.get("LIVE"))
		)
	);
}

// index.tsx
class VoiceActivity {
	meta;
	contextMenuUnpatches = new Set();
	constructor(meta) {
		this.meta = meta;
	}
	start() {
		showChangelog(changelog, this.meta);
		betterdiscord.DOM.addStyle(
			styles() + `${memberSelectors?.children}:empty { margin-left: 0; } ${memberSelectors?.children} { display: flex; gap: 8px; } ${memberSelectors?.layout} { width: 100%; }`
		);
		Strings.subscribe();
		this.patchPeopleListItem();
		this.patchMemberListItem();
		this.patchPrivateChannel();
		this.patchChannelContextMenu();
		this.patchGuildContextMenu();
		betterdiscord.Patcher.instead(VoiceStateStore, "getDiscoverableVoiceState", (_, [guildId, userId]) => {
			return VoiceStateStore.getDiscoverableVoiceStateForUser(userId);
		});
	}
	patchMemberListItem() {
		if (!MemberListItem) return;
		betterdiscord.Patcher.after(MemberListItem, "type", (_, [props], ret) => {
			if (!props.user) return ret;
			const children = ret.props.children;
			ret.props.children = (childrenProps) => {
				const childrenRet = children(childrenProps);
				const target = betterdiscord.Utils.findInTree(childrenRet, (x) => x.props?.avatar && x.props?.decorators, {
					walkable: ["props", "children"]
				});
				const icon = BdApi.React.createElement(VoiceIcon, { userId: props.user.id, context: "memberlist" });
				Array.isArray(target.props.children) ? target.props.children.unshift(icon) : target.props.children = [icon];
				return childrenRet;
			};
		});
	}
	patchPrivateChannel() {
		if (!PrivateChannel) return;
		const patchType = (props, ret) => {
			if (props.channel.type !== 1) return;
			const target = betterdiscord.Utils.findInTree(ret, (e) => typeof e?.props?.children !== "function", {
				walkable: ["children", "props"]
			})?.props?.children ?? ret;
			if (!target) return;
			const children = target.props.children;
			target.props.children = (childrenProps) => {
				const childrenRet = children(childrenProps);
				const privateChannel = betterdiscord.Utils.findInTree(childrenRet, (e) => e?.children?.props?.avatar, {
					walkable: ["children", "props"]
				});
				privateChannel.children = [
					privateChannel.children,
					BdApi.React.createElement("div", { className: iconStyles.iconContainer }, BdApi.React.createElement(VoiceIcon, { userId: props.user.id, context: "dmlist" }))
				];
				return childrenRet;
			};
		};
		let patchedType;
		betterdiscord.Patcher.after(...PrivateChannel, (_, __, containerRet) => {
			let target = containerRet.children || containerRet;
			if (patchedType) {
				target.type = patchedType;
				return containerRet;
			}
			const original = target.type;
			patchedType = (props) => {
				const ret = original(props);
				patchType(props, ret);
				return ret;
			};
			target.type = patchedType;
		});
	}
	patchPeopleListItem() {
		if (!PeopleListItem) return;
		betterdiscord.Patcher.after(PeopleListItem.prototype, "render", (that, _, ret) => {
			if (!that.props.user) return;
			const children = ret.props.children;
			ret.props.children = (childrenProps) => {
				const childrenRet = children(childrenProps);
				betterdiscord.Utils.findInTree(childrenRet, (i) => Array.isArray(i), { walkable: ["props", "children"] }).splice(
					1,
					0,
					BdApi.React.createElement("div", { className: `${iconStyles.iconContainer} ${iconStyles.peopleListIcon}` }, BdApi.React.createElement(VoiceIcon, { userId: that.props.user.id, context: "peoplelist" }))
				);
				return childrenRet;
			};
		});
	}
	patchChannelContextMenu() {
		const unpatch = betterdiscord.ContextMenu.patch("channel-context", (ret, props) => {
			if (!Settings.get("ignoreEnabled")) return ret;
			if (props.channel.type !== 2 && props.channel.type !== 13) return ret;
			const { ignoredChannels } = Settings.useSettingsState("ignoredChannels");
			const ignored = ignoredChannels.includes(props.channel.id);
			const menuItem = betterdiscord.ContextMenu.buildItem({
				type: "toggle",
				label: Strings.get("CONTEXT_IGNORE"),
				id: "voiceactivity-ignore",
				checked: ignored,
				action: () => {
					if (ignored) {
						const newIgnoredChannels = ignoredChannels.filter((id) => id !== props.channel.id);
						Settings.set("ignoredChannels", newIgnoredChannels);
					} else {
						const newIgnoredChannels = [...ignoredChannels, props.channel.id];
						Settings.set("ignoredChannels", newIgnoredChannels);
					}
				}
			});
			ret.props.children[3].props.children.splice(4, 0, menuItem);
		});
		this.contextMenuUnpatches.add(unpatch);
	}
	patchGuildContextMenu() {
		const unpatch = betterdiscord.ContextMenu.patch("guild-context", (ret, props) => {
			if (!props.guild) return ret;
			if (!Settings.get("ignoreEnabled")) return ret;
			const { ignoredGuilds } = Settings.useSettingsState("ignoredGuilds");
			const ignored = ignoredGuilds.includes(props.guild.id);
			const menuItem = betterdiscord.ContextMenu.buildItem({
				type: "toggle",
				label: Strings.get("CONTEXT_IGNORE"),
				id: "voiceactivity-ignore",
				checked: ignored,
				action: () => {
					if (ignored) {
						const newIgnoredGuilds = ignoredGuilds.filter((id) => id !== props.guild.id);
						Settings.set("ignoredGuilds", newIgnoredGuilds);
					} else {
						const newIgnoredGuilds = [...ignoredGuilds, props.guild.id];
						Settings.set("ignoredGuilds", newIgnoredGuilds);
					}
				}
			});
			ret.props.children[2].props.children.push(menuItem);
		});
		this.contextMenuUnpatches.add(unpatch);
	}
	stop() {
		betterdiscord.DOM.removeStyle();
		betterdiscord.Patcher.unpatchAll();
		this.contextMenuUnpatches.forEach((unpatch) => unpatch());
		this.contextMenuUnpatches.clear();
		Strings.unsubscribe();
	}
	getSettingsPanel() {
		return buildSettingsPanel(Settings, [
			{
				id: "showMemberListIcons",
				type: "switch",
				name: Strings.get("SETTINGS_ICONS"),
				note: Strings.get("SETTINGS_ICONS_NOTE")
			},
			{
				id: "showDMListIcons",
				type: "switch",
				name: Strings.get("SETTINGS_DM_ICONS"),
				note: Strings.get("SETTINGS_DM_ICONS_NOTE")
			},
			{
				id: "showPeopleListIcons",
				type: "switch",
				name: Strings.get("SETTINGS_PEOPLE_ICONS"),
				note: Strings.get("SETTINGS_PEOPLE_ICONS_NOTE")
			},
			{
				id: "currentChannelColor",
				type: "switch",
				name: Strings.get("SETTINGS_COLOR"),
				note: Strings.get("SETTINGS_COLOR_NOTE")
			},
			{
				id: "showStatusIcons",
				type: "switch",
				name: Strings.get("SETTINGS_STATUS"),
				note: Strings.get("SETTINGS_STATUS_NOTE")
			},
			{
				id: "currentUserIcon",
				type: "switch",
				name: Strings.get("SETTINGS_CURRENT_USER"),
				note: Strings.get("SETTINGS_CURRENT_USER_NOTE")
			},
			{
				id: "ignoreEnabled",
				type: "switch",
				name: Strings.get("SETTINGS_IGNORE"),
				note: Strings.get("SETTINGS_IGNORE_NOTE")
			}
		]);
	}
}

module.exports = VoiceActivity;

/*@end@*/